Does writing const auto& [a, b] = f();
guarantee extending the lifetime of the object returned from f()
, or at least the objects a
and b
are bound to? Reading through the proposal I don't see anything obvious in the language to make me sure that it does unless it's just covered by something else. However, the following doesn't extend the lifetime of the temporary, so I don't see how it would be covered:
const auto& a = std::get<0>(f());
At the top of the paper it seems to suggest that it is covered
the cv-qualifiers and ref-qualifier of the decomposition declaration are applied to the reference introduced for the initializer, not for the individual member aliases
But in the proposed wording for the actual standard, the closest mention I see is below, though I'm not sure how to read it to get the guarantee I'm looking for:
if e is an unparenthesized id-expression naming an lvalue or reference introduced from the identifier-list of a decomposition declaration, decltype(e) is the referenced type as given in the specification of the decomposition declaration
It seems that gcc and clang both extend the lifetime of the object returned until the end of the scope based on a wandbox experiment. An uglier one implementing all the bells and whistles for my own type seems to extend the lifetime of the outer object and its other data members.
Though almost certainly the authors' intent(s), I'd like to know for sure that the language guarantees this is safe.
You are not questioning why const references are allowed to bind to temporaries, but merely why they extend the lifetime of those temporaries. If the lifetime of the temporary returned by bar() were not extended, then any usage of a (exemplified by the line (1)) would lead to undefined behavior.
Binds the specified names to subobjects or elements of the initializer. Like a reference, a structured binding is an alias to an existing object. Unlike a reference, a structured binding does not have to be of a reference type.
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
e
is introduced. If the assignment-expression in the initializer has array typeA
and no ref-qualifier is present,e
has typecv A
and 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,e
is defined as-if byattribute-specifier-seqoptdecl-specifier-seq ref-qualifieropt
e
initializer ;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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With