Yes. The trick is to realize that despite the appearance, the portion of a structured binding declaration before the [ doesn’t apply to the names in the identifier-list. They apply instead to the variable introduced implicitly by the declaration. [dcl.struct.bind]/1:
First, a variable with a unique name
eis introduced. If the
assignment-expression in the initializer has array typeAand no ref-qualifier is present,ehas typecv Aand each element is copy-initialized or direct-initialized from the corresponding element
of the assignment-expression as specified by the form of the
initializer. Otherwise,eis defined as-if byattribute-specifier-seqopt decl-specifier-seq ref-qualifieropt
einitializer ;where the declaration is never interpreted as a function declaration
and the parts of the declaration other than the declarator-id are
taken from the corresponding structured binding declaration.
The names are then defined to either be aliases for the elements of e or references bound to the result of calling get on e.
In your example, it’s as if by (assuming that f returns a two-element std::tuple):
const auto& e = f(); // 1
using E = remove_reference_t<decltype((e))>;
std::tuple_element<0, E>::type& a = get<0>(e);
std::tuple_element<1, E>::type& b = get<1>(e);
(Except that decltype(a) and decltype(b) gets the special treatment to hide their referenceness.)
It should be pretty obvious that line #1 does extend the lifetime of f‘s return value.