Is capturing a newly constructed object by const ref undefined behavior - c++

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)!

Related

Does a constant reference member variable in an anonymous struct extend the lifetime of a temporary?

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 returning a reference to a local object undefined behavior in copy initialization?

Consider the following code:
struct foo
{
foo(foo const&) = default; // To make sure it exists
};
foo& get_local_foo_reference()
{
foo my_local_foo;
return my_local_foo; // Return a reference to a local variable
}
int main()
{
foo my_foo = get_local_foo_reference();
}
Now everybody would agree that returning a reference to a local variable is bad and lead to undefined behavior.
But in the case of copy initialization (as shown in the code above) the argument is a constant lvalue reference, so it should be a reference initialization of the argument, which extends the life-time of the reference.
Is this valid, or is it still undefined behavior?
Lifetime extension only applies to temporaries when bounds to const reference or r-value reference. (temporary cannot be bound to non const l-value reference)
Even if you return temporary, it would be UB:
const foo& create_foo() { return foo{}; } // Also UB
From http://eel.is/c++draft/class.temporary#6.10:
The lifetime of a temporary bound to the returned value in a function return statement is not extended; the temporary is destroyed at the end of the full-expression in the return statement.
Lifetime extension only applies to temporaries (and subobjects thereof). my_local_foo is not a temporary.

Member rvalue references and object lifetime

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.

Is the lifetime of a C++ temporary object created in ?: expression extended by binding it to a local const reference?

It is not clear to me whether the lifetime of a temporary object would be extended by binding it to a const reference in a ?: expression:
class Foo {...};
Foo *someLValue = ...;
const Foo& = someLValue ? *someLValue : Foo();
Is the lifetime of the temporary created by calling the default constructor Foo() extended by binding it to the local const ref even though the binding is conditional? Or does this create a dangling reference because the temporary value of Foo() would be destroyed at the end of the ?: expression?
In this code, the second and third operand of the conditional operator have different value categories (lvalue and prvalue).
That means that the result of the conditional operator is a prvalue of type Foo, which denotes a temporary object copy-initialized from the selected operand.
The reference binds directly to this temporary object and so the temporary's lifetime is extended.
Notes:
The reference never binds directly to *someLValue, nor even to Foo().
The temporary being initialized from Foo() is a copy elision context so you may not be able to observe the temporary in this case.
The temporary is non-const even though the reference is to const.

Will the lifetime of a temporary created in the argument list to a function taking a *non*-const reference parameter encompass the function call?

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.