It's been a while since I last looked at temporary lifetime rules and I don't remember how member rvalue references affect
lifetime.
For example take the two following pieces of code:
int main()
{
std::get<0>(std::forward_as_tuple(
[](int v){ std::cout << v << std::endl; }
))(6);
return 0;
}
,
int main()
{
auto t = std::forward_as_tuple(
[](int v){ std::cout << v << std::endl; }
);
std::get<0>(t)(6);
return 0;
}
If member rvalue references don't affect lifetime rules, I would expect the first example to be well-behaved while the second example
to be undefined (since the full-expression containing the lambda object
ends at the first semicolon).
How do C++11, C++14 and C++17 treat the given examples? Are there differences between the three?
Lifetime extension applies when you directly bind a temporary to a reference anywhere but within a constructor initializer list. (Note: aggregate initialization isn't a constructor)
std::forward_as_tuple is a function. Any temporary passed to it cannot be lifetime extended beyond the current line.
Temporaries by default last until the end of the current line, basically. (What exactly that spot is isn't actually the end of the current line). In your two cases, it is the end of the current line (the ;) where the temporary ends its lifetime. This is long enough for the first case; in the second case, the temporary is dead and your code exhibits undefined behavior.
At the same time:
struct foo {
int&& x;
};
int main() {
foo f{3};
std::cout << f.x << "\n";
}
is perfectly well defined. No constructor, we bound a temporary to an (rvalue) reference, thus lifetime is extended.
Add this:
struct foo {
int&& x;
foo(int&& y):x(y) {}
};
or
struct foo {
int&& x;
foo(int y):x((int)y) {}
};
and it is now UB.
The first one because we bound the temporary to an rvalue reference when invoking the ctor. The inside of the constructor is irrelevant, as there is no temporary being bound directly there. Then the argument to the function and the temporary both go out of scope in main.
The second because the rule that binding the temporary (int)y0 to int&&x in a constructor initializer list does not extend lifetime the same way as it does elsewhere.
The rules of temporary lifetime extension haven't changed in any version of C++ since 98. We may have new ways to manifest temporaries, but once they exist, their lifetimes are well understood.
It should be noted that your example doesn't really apply to member references of any kind. You're calling a function with a temporary, and the parameter is a forwarding reference. The temporary in question is therefore bound to the function parameter reference, not to a member reference. The lifetime of that temporary will end after the expression that invoked that function, as it would with any temporary passed to a reference parameter.
The fact that this function (forward_as_tuple) will eventually store that reference in a tuple is irrelevant. What you do with a reference cannot alter its lifetime.
Again, that's C++98, and none of the later versions changed that.
Because this is a language-lawyer question. The rules for lifetime extension are in [class.temporary]. The wording from C++11 to C++14 to C++17 didn't change in a way that is relevant to this particular question. The rule is:
There are [two/three†] contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context is when a default constructor is called to initialize an element of an array [...]†
The [second/third†] context is when a reference is bound to a temporary. The temporary to which the reference is
bound or the temporary that is the complete object of a subobject to which the reference is bound persists
for the lifetime of the reference except:
— A temporary object bound to a reference parameter in a function call (5.2.2) persists until the completion
of the full-expression containing the call.
This expression:
std::forward_as_tuple([](int v){ std::cout << v << std::endl; })
involves binding a reference (the parameter in forward_as_tuple) to a prvalue (the lambda-expression), which is explicitly mentioned in C++11/14 as a context which creates a temporary:
Temporaries of class type are created in various contexts: binding a reference to a prvalue, [...]
which in C++17 is worded as:
Temporary objects are created
(1.1) — when a prvalue is materialized so that it can be used as a glvalue (4.4),
Either way, we have a temporary, it's bound to a reference in a function call, so the temporary object persists until the completion of the full-epxression containing the call.
So this is okay:
std::get<0>(std::forward_as_tuple(
[](int v){ std::cout << v << std::endl; }
))(6);
But this calls through a dangling reference:
auto t = std::forward_as_tuple(
[](int v){ std::cout << v << std::endl; }
);
std::get<0>(t)(6);
because the lifetime of the temporary function object ended at the end of the statement initializing t.
Note that this has nothing to do with member rvalue references. If we had something like:
struct Wrapper {
X&& x;
};
Wrapper w{X()};
then the lifetime of the temporary X persists through the lifetime of w, and w.x isn't a dangling reference. But that's because there's no function call.
†C++17 introduced a 3rd context which involves copying an array, which is unrelated here.
Related
Consider the following code:
struct Temp{ int i = 0; };
Temp GetTemp() { return Temp{}; }
int main()
{
for (struct{Temp const & t; int counter;} v = {GetTemp(), 0};
v.counter < 10;
++v.counter)
{
// Is v.t guaranteed to not be a dangling reference?
std::cout << v.t.i << std::endl;
}
}
So GetTemp() returns a temporary object, which then gets assigned to a constant reference variable. However, that constant reference variable is a member of an anonymous local struct. Question: Does the C++ standard guarantee that the lifetime of that temporary gets extended till after the loop terminates?
Considering this question, I would have expected the answer to be no, i.e. that I get a dangling reference in the loop body. However, gcc and clang seem to extend the lifetime (see example on godbolt), and even do not complain with -fsanitize=undefined, which surprised me.
For braced aggregate initialization as in your example, lifetime extension has been guaranteed since C++98 (irrespective of the linkage/visibility properties of the class). This is intuitive, since the reference is directly bound to the temporary, and not via some intermediate ctor parameter, as in the question you've linked. For legalese, see [class.temporary] in the C++14 FD that outlines the lifetime extension contexts.
See also the note here which differentiates braced and parenthetical initialization since C++20:
[Note 7: By contrast with direct-list-initialization, narrowing
conversions ([dcl.init.list]) are permitted, designators are not
permitted, a temporary object bound to a reference does not have its
lifetime extended ([class.temporary]), and there is no brace elision.
[Example 3:
struct A {
int a;
int&& r;
};
int f();
int n = 10;
A a1{1, f()}; // OK, lifetime is extended
A a2(1, f()); // well-formed, but dangling reference
Does a constant reference member variable in an anonymous struct extend the lifetime of a temporary?
Yes, the chain of const references is unbroken. There's is only v and v is alive until the end of the for loop and the lifetime of the referenced Temp is therefore extended until then. The fact that the struct is anonymous has no impact.
class.temporary/4
There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context is when a default constructor is called to initialize an element of an array. If the constructor has one or more default arguments, the destruction of every temporary created in a default argument is sequenced before the construction of the next array element, if any.
class.temporary/5
The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except:
(5.1) A temporary bound to a reference member in a constructor's ctor-initializer ([class.base.init]) persists until the constructor exits.
(5.2) A temporary bound to a reference parameter in a function call ([expr.call]) persists until the completion of the full-expression containing the call.
(5.3) The lifetime of a temporary bound to the returned value in a function return statement ([stmt.return]) is not extended; the temporary is destroyed at the end of the full-expression in the return statement.
(5.4) A temporary bound to a reference in a new-initializer ([expr.new]) persists until the completion of the full-expression containing the new-initializer.
None of the exceptions to the lifetime extension applies to your case.
Is the following (contrived example) okay or is it undefined behavior:
// undefined behavior?
const auto& c = SomeClass{};
// use c in code later
const auto& v = c.GetSomeVariable();
It is safe. Const ref prolongs the lifetime of temporary. The scope will be the scope of const ref.
The lifetime of a temporary object may be extended by binding to a
const lvalue reference or to an rvalue reference (since C++11), see
reference initialization for details.
Whenever a reference is bound to a temporary or to a subobject
thereof, the lifetime of the temporary is extended to match the
lifetime of the reference, with the following exceptions:
a temporary bound to a return value of a function in a return statement is not extended: it is destroyed immediately at the end of
the return expression. Such function always returns a dangling
reference.
a temporary bound to a reference member in a constructor initializer list persists only until the constructor exits, not as
long as the object exists. (note: such initialization is ill-formed as
of DR 1696).
a temporary bound to a reference parameter in a function call exists until the end of the full expression containing that function
call: if the function returns a reference, which outlives the full
expression, it becomes a dangling reference.
a temporary bound to a reference in the initializer used in a new-expression exists until the end of the full expression containing
that new-expression, not as long as the initialized object. If the
initialized object outlives the full expression, its reference member
becomes a dangling reference.
a temporary bound to a reference in a reference element of an aggregate initialized using direct-initialization syntax (parentheses)
as opposed to list-initialization syntax (braces) exists until the end
of the full expression containing the initializer.
struct A {
int&& r;
};
A a1{7}; // OK, lifetime is extended
A a2(7); // well-formed, but dangling reference
In general, the lifetime of a temporary cannot be further extended by "passing it
on": a second reference, initialized from the reference to which the
temporary was bound, does not affect its lifetime.
as #Konrad Rudolph pointed out (and see the last paragraph of above):
"If c.GetSomeVariable() returns a reference to a local object or a reference that it is itself extending some object’s lifetime, lifetime extension does not kick in"
There should be no issue here, thanks to lifetime extension. The newly constructed object will survive until the reference goes out of scope.
Yes this is perfectly safe: the binding to a const reference extends the lifetime of the temporary to the scope of that reference.
Note that the behaviour is not transitive though. For example, with
const auto& cc = []{
const auto& c = SomeClass{};
return c;
}();
cc dangles.
This is safe.
[class.temporary]/5: There are three contexts in which temporaries are destroyed at a different point than the end of the full-expression. [..]
[class.temporary]/6: The third context is when a reference is bound to a temporary object. The temporary object to which the reference is bound or the temporary object that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference if the glvalue to which the reference is bound was obtained through one of the following: [lots of things here]
It is safe in this specific case. Note however that not all temporaries are safe to capture by const reference... for example
#include <stdio.h>
struct Foo {
int member;
Foo() : member(0) {
printf("Constructor\n");
}
~Foo() {
printf("Destructor\n");
}
const Foo& method() const {
return *this;
}
};
int main() {
{
const Foo& x = Foo{}; // safe
printf("here!\n");
}
{
const int& y = Foo{}.member; // safe too (special rule for this)
printf("here (2)!\n");
}
{
const Foo& z = Foo{}.method(); // NOT safe
printf("here (3)!\n");
}
return 0;
}
The reference obtained for z is NOT safe to use because the temporary instance will be destroyed at the end of full expression, before reaching the printf statement. Output is:
Constructor
here!
Destructor
Constructor
here (2)!
Destructor
Constructor
Destructor
here (3)!
Following the well accepted answer to this question Do rvalue references allow dangling references? It would seem that xvalues do not have their lifetime extended when assigned to a rvalue reference lvalue like in the question. However when I do this
#include <iostream>
using namespace std;
class Something {
public:
Something() {
cout << "Something()" << endl;
}
Something(const Something&) {
cout << "Something(const Something&)" << endl;
}
Something(Something&&) {
cout << "Something(Something&&)" << endl;
}
~Something() {
cout << "~Something()" << endl;
}
int a;
};
Something make_something() {
return Something{};
}
int main() {
auto&& something = make_something().a;
return 0;
}
The lifetime of the object returned by a call to make_something is extended, even though make_something().a is an xvalue as per http://en.cppreference.com/w/cpp/language/value_category (the third bullet in the xvalues explanation lists the member access I have above as an xvalue,)
a.m, the member of object expression, where a is an rvalue and m is a
non-static data member of non-reference type;
If value categories do not determine when the lifetime of an rvalue will be extended then what does? I am having a hard time understanding when the lifetime of an rvalue is extended in C++
Lifetime extension doesn't care about value categories. As stated by [class.temporary]/p6:
The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference
Emphasis added.
This says nothing here about the value category of the expression being referenced.
What determines whether a temporary is extended is exactly the above (and a few more rules).
But that does not explain why adding an std::move() around the temporary to which the reference is being assigned does not extend the lifetime
std::move is not a magical, compiler-defined construct in C++. It is a function call, and therefore it behaves no differently from any other C++ function call.
So, if you have std::move(Type()), what does that mean? It means that you will create a temporary, bind it to the parameter of std::move, then call that function, which will return something.
Binding a temporary to a function parameter, as stated in [class.temporary]/p6, means that the lifetime of the temporary is fixed to be the lifetime of the full expression that created it (if not for that rule, then the temporary would have to be destroyed at the end of the function call, since that's the end of the reference's lifetime).
It doesn't matter what the function does, says, or implies. It doesn't matter if the compiler could perhaps inline things and determine that the return value is a reference to an argument that came from a temporary. The lifetime of that temporary is fixed to the expression, not extended.
If value categories do not determine when the lifetime of an rvalue will be extended then what does? I am having a hard time understanding when the lifetime of an rvalue is extended in C++
Note that value categories describe expressions not objects. Value categories ( xvalue, prvalue, or whatever ) won't be extended in any way. Only objects can have a lifetime.
From the n4296 standard draft:
§12.2.1
Temporaries of class type are created in various contexts: binding a reference to a prvalue (8.5.3), returning
a prvalue (6.6.3), a conversion that creates a prvalue (4.1, 5.2.9, 5.2.11, 5.4), throwing an exception (15.1),
and in some initializations (8.5).
and
§12.2.4
There are two contexts in which temporaries are destroyed at a different point than the end of the full-
expression. [...]
The second context is when a reference is bound to a temporary. The temporary to which the reference is
bound or the temporary that is the complete object of a subobject to which the reference is bound persists
for the lifetime of the reference
Note: I didn't quote the first context since it has minor revelance regarding the questions. Italic emphasis added by me.
Thus the value category of the function expression makesomething() is a prvalue creating a temporary of class type, according to the first paragraph cited above.
makesomething().a accesses a temporary, complete subobject. Binding this temporary to a reference leads, according to the second context quoated above, to an extended lifetime.
The lifetime of the subobject a is coupled to the lifetime of the previous created temporary making it an expiring value (xvalue). Without extending its lifetime by binding it to a reference it would be destroyed together with the temporary class object. Thus, in this case, after the ;.
Once upon a time, I assumed that code like this would fail:
const MyClass& obj = MyClass();
obj.DoSomething();
because the MyClass object would be destroyed at the end of its full-expression, leaving obj as a dangling reference. However, I learned (here) that this isn't true; the standard actually has a special provision that allows const references to keep temporaries alive until said references are destroyed themselves. But, it was emphasized, only const references have this power. Today I ran the code below in VS2012 as an experiment.
struct Foo
{
Foo() { std::cout << "ctor" << std::endl; }
~Foo() { std::cout << "dtor" << std::endl; }
};
void f()
{
Foo& f = Foo();
std::cout << "Hello world" << std::endl;
}
The output when calling f() was:
ctor
Hello world
dtor
So I had a look at the C++11 draft standard, and only found this (§ 12.2/4):
There are two contexts in which temporaries are destroyed at a
different point than the end of the full-expression. The first context [doesn't
apply]. The second context is when a reference is bound to a
temporary. The temporary to which the reference is bound or the
temporary that is the complete object of a subobject to which the
reference is bound persists for the lifetime of the reference.
The word const is conspicuously absent from the above. So; has this behavior been changed for C++11, was I wrong about the const thing to begin with, or does VS2012 have a bug and I just haven't found the relevant part of the standard?
The behavior hasn't changed, you just need to turn your warning level up to /W4. VisualStudio implements the lifetime extension rule even for non-const lvalue references as a compiler extension. In this context, binding an rvalue to the non-const reference behaves the same as if you were binding it to a const reference.
With /W4 you'd see this:
warning C4239: nonstandard extension used : 'initializing' : conversion from 'Foo' to 'Foo &'
1> A non-const reference may only be bound to an lvalue
The text disallowing binding of an rvalue to a non-const lvalue reference can be found in §8.5.3/5
— Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be
const), or the reference shall be an rvalue reference.[ Example:
double& rd2 = 2.0; // error: not an lvalue and reference not const
int i = 2;
double& rd3 = i; // error: type mismatch and reference not const
—end example ]
The second half of the quoted statement is what allows binding of a temporary to an rvalue reference, as shown in litb's answer.
string &&s = string("hello");
This, combined with the lifetime extension rule in §12.2/5, means the lifetime of the temporary will now match the lifetime of the (rvalue) reference it is bound to.
The word const was never present in this section. The rule
has always been (from as long as I can remember) that
a temporary used to initialize a reference has its lifetime
extended to match that of the reference, regardless of the type
of the reference.
Sometime in the late 1980's (very pre-standard), C++ introduced
the rule that a temporary could not be used to initialize
a non-const reference. Initializing a non-const reference with
a temporary would still extend the lifetime (presumably), but
since you couldn't do it... Most compilers implemented
a transition period, in which such an initialization would only
emit a warning (and the lifetime was extended).
For some reason, when Microsoft finally decided to implement
C++ (some time in the early 1990's), they decided not to
implement the new rule, and allowed initialization of
a non-const reference with a temporary (without even a warning,
at a time when most other vendors were gradually turning the
warning into an error). And of course, the implemented the
usual lifetime rule.
Finally, in C++11, new types of references were introduced,
which allowed (or even required) initialization with
a temporary. The rule about the lifetime of temporaries hasn't
changed, though; a temporary which is used to initialize
a reference (regardless of the type of reference) has its
lifetime extended.
(With a few exceptions: I would not recommend using a temporary
to initialize a class member reference in an initialization
list.)
No, because rvalue references don't need to be const, so the Standard quote is correct
string &&s = string("hello");
The lifetime is still enlargened. The constraints that make the code invalid for non-const lvalue reference is at clause 8 (notice that it is not the right place to just add "const" and "rvalue reference" etc in the paragraph you quoted. You need an active rejection of such bindings, not just saying that the lifetime of such bindings are not enlarged because you would leave the binding itself still wellformed).
I have become accustomed to the fact that a const reference extends the lifetime of a temporary until the reference goes out of scope:
class X {};
{
X const & x = X();
// Lifetime of x extended to the end of the scope
// in which x is declared because it was declared
// as a *const* reference
}
... And I am also aware that a temporary lives until the end of the expression in which it is created:
// Contrived example to make a point about lifetime of a temporary in an expression
class Y
{
public:
Y() : y(5) {}
Y & operator+=(int const & rhs)
{
y += rhs;
return *this;
}
int foo() { return y; }
private:
int y;
};
// n in the following line of code is 11 and the code is valid
// - the lifetime of the temporary persists to the end of the expression
int n = (Y() += 6).foo();
Assuming I am correct about both of the above, I suspect that it is true that a temporary created in a function argument list will persist for the lifetime of the function call even if it is bound to a non-const reference:
class X {};
void foo(X & x)
{
// x is valid in this function,
// even though the parameter is declared
// as a *non*-const reference - correct?
}
// Valid, I think, even though the function parameter
// is declared as a **non**-const reference
// - because the lifetime of the temporary persists until the expression
// is fully evaluated - right?
foo(X());
I imagine that my experience and understanding is correct - that it is safe to bind the temporary created in the function argument list to the non-const reference parameter.
But I'd like to confirm I am right about this, because I was not able to find this question explicity answered anywhere.
Thanks!
Sure you are right.
The standard:
12.2 Temporary objects [class.temporary]
[...]
There are two contexts in which temporaries are destroyed at a different point than the end of the fullexpression.
The first context is when a default constructor is called to initialize an element of an array. If
the constructor has one or more default arguments, the destruction of every temporary created in a default
argument is sequenced before the construction of the next array element, if any.
The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except:
— A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits.
— A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call.
— The lifetime of a temporary bound to the returned value in a function return statement (6.6.3) is not extended; the temporary is destroyed at the end of the full-expression in the return statement.
— A temporary bound to a reference in a new-initializer (5.3.4) persists until the completion of the
full-expression containing the new-initializer.
Want to ask the question again using one more level of constructors, and objects storing references?
Deduplicator has given the language lawyer answer, here is the implementation detail answer:
If you pass any object by reference, you are effectively passing a pointer to that object. Since the caller is only passing a pointer to the object, the object that is passed by reference must be constructed by the caller.
The callee, on the other hand, only ever sees the pointer that is passed in. It does not see how the caller has constructed the object whose pointer is passed. This object could be a temporary (only in the case of a const reference, because temporaries cannot be passed as non-const references), or a variable with function scope, it could be an object that was already passed to the caller by its caller, it could even be an object allocated with new. As such, it cannot destruct the object because it does not know how.
Consequently, it is up to the caller to clean up the temporary after the callee returns control to the caller - anything that is passed in by reference must live for the entire runtime of the callee.
Note that this entire argument is completely ignorant as to whether the reference is const or not.