Consider the following functions. I'd like answers for C++17.
MyClass&& func() {
return MyClass{};
}
int main() {
MyClass&& myRef = func();
}
Questions:
Is the expression func() an xvalue? Why?
Why is myRef a dangling reference? Or, more specifically, why is func() returning a dangling reference? Wouldn't returning rvalue reference cause temporary materialization, and extend the temporary object's lifetime?
func() is an xvalue because one of the rules of the language is that if a function is declared to have a return type of rvalue reference to object, then an expression consisting of calling that function is an xvalue . (C++17 expr.call/11).
Temporary materialization occurs any time a reference is bound to a prvalue.
The result of the function is myRef which is initialized by the prvalue func(). However if we consult the lifetime extension rules in class.temporary/6 it has:
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.
So the temporary object materialized by func() is destroyed when the return statement completes, with no extension.
Related
According to What are rvalues, lvalues, xvalues, glvalues, and prvalues? and some other explanations, my understanding is that xvalue is the expression which has identity and is safely moved (or is so marked).
Some texts like this and this say that, if a function f()'s return type is rvalue reference, then the expression f() is xvalue. For example:
int&& f() {
return 1;
}
int main() {
f(); // xvalue
2; // prvalue
}
My confusion is that, because the origin of f() is the literal 1, for me f() doesn't seem to have an identity and thus I can't understand how it becomes xvalue. If 1 has identity, why is 2 said to have no identity and is prvalue? Does prvalue suddenly have "identity" if it's returned from a function as an rvalue reference?
EDIT
It's pointed out that f() returns a dangling reference, but I hope my point still makes sense.
EDIT2
Well, after reading the (very helpful) comments, it seems that it probably doesn't make sense?
Does prvalue suddenly have "identity" if it's returned from a function as an rvalue reference?
Yes, actually. The standard pretty much says that outright:
[conv.rval]
A prvalue of type T can be converted to an xvalue of type T. This conversion initializes a temporary object ([class.temporary]) of type T from the prvalue by evaluating the prvalue with the temporary object as its result object, and produces an xvalue denoting the temporary object.
That temporary object, while it exists, most certainly has "identity". Of course, the result of such a conversion is no longer a prvalue, so perhaps we shouldn't say the prvalue "gets an identity." Note that this works, too, also because of temporary materialization:
(int&&)1; // This is different from f(), though, because that reference is dangling but I believe this one isn't (lifetime extension of a temporary by binding to a reference applies here but is suppressed for a return)
Note that the operand of a return statement and the thing that actually gets returned simply don't have to be the same thing. You give an int prvalue, you need an int xvalue, the return makes it work by materializing a temporary. It's not obliged to fail because of the mismatch. Unfortunately, that temporary immediately gets destroyed when the return statement ends, leaving the xvalue dangling, but, for that moment in between the returned reference being bound and the temporary being destroyed, yes, the rvalue reference indeed referred to an object with its own identity.
Other examples of prvalues being materialized so you can bind references to them:
int &&x = 1; // acts just like int x = 1 except for decltype and similar considerations
int const &y = 1; // ditto but const and also available in older C++ versions
// in some imaginary API
void install_controller(std::unique_ptr<controller> &&new_controller) {
if(can_replace_controller()) current_controller = std::move(new_controller);
}
install_controller(std::make_unique<controller>("My Controller"));
// prvalue returned by std::make_unique materializes a temporary, binds to new_controller
// might be moved from, might not; in latter case new pointer (and thus object)
// is destroyed at full-expression end (at the semicolon after the function call)
// useful to take by reference so you can do something like
auto p = std::make_unique<controller>("Perseverant Controller");
while(p) { wait_for_something(); install_controller(std::move(p)); }
Other examples of return not being trivial:
double d(int x) { return x; }
// int lvalue given to return but double prvalue actually returned! the horror!
struct dangerous {
dangerous(int x) { launch_missiles(); }
};
dangerous f() { return 1; }
// launch_missiles is called from within the return!
std::vector<std::string> init_data() {
return {5, "Hello!"};
}
// now the operand of return isn't even a value/expression!
// also, in terms of temporaries, "Hello!" (char const[7] lvalue) decays to a
// char const* prvalue, converts to a std::string prvalue (initializing the
// parameter of std::string's constructor), and then that prvalue materializes
// into a temporary so that it can bind to the std::string const& parameter of
// std::vector<std::string>'s constructor
Here I try to summarize my understanding after reading the given comments.
The whole purpose of returning an rvalue reference is to use it in some way, so returning an rvalue reference that points to a function local object, which is already invalid when the function returns, is not considered (well, I'm sure the committee does consider this of course, but not as an intended usage).
As a result, if I have a function T&& f() { /.../ return val; }, val is supposed to locate somewhere with its identity even after f() returns, otherwise it's dangling which is a mere error. Therefore, the intention that f() has an identity, so is an xvalue, is justified.
To be honest, I find the whole concept of "having identity" somewhat moot.
Here's how I tend to think about it:
A prvalue is an expression that creates an object.
An rvalue is an expression denoting a temporary object (or an object considered to be temporary, e.g. because it was std::moved).
An lvalue is an expression denoting a non-temporary object (or an object considered to be non-temporary).
A call to int &&f() {...} doesn't create a new object (at least if we ignore the function body, and only look at the function-calling mechanism itself), so the result is not a prvalue (but it's obviously an rvalue, thus it's also an xvalue).
A call to int f() {...}, on the other hand, unconditionally creates an object (the temporary int; regardless of the function body), so it's a prvalue.
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)!
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.
According to another answer, an rvalue reference will not extend the lifetime of a temporary if the expression referring to it is an xvalue expression. Since std::move returns an rvalue reference, the expression of calling it is an xvalue and so the following results in an a dangling reference:
int main()
{
std::string&& danger = std::move(get_string()); // dangling reference !
return 0;
}
That's fine. The std::move doesn't make sense here; it is already an rvalue.
But here's where I'm drawing a blank. How is this different to passing an xvalue expression as an argument, completely standard use of std::move and rvalue references?
void foo(const std::string& val);
// More efficient foo for temporaries:
void foo(std::string&& val);
int main()
{
std::string s;
foo(std::move(s)); // Give up s for efficiency
return 0;
}
Is there a special rule for rvalue reference arguments that will extend the lifetime of a temporary regardless of whether it is an prvalue or xvalue? Or is the std::move calling expression only an xvalue because we passed it something that was already an rvalue? I don't think so because it returns an rvalue reference anyway, which is an xvalue. I'm confused here. I think I'm missing something silly.
Your second example is not passing a reference to a temporary, it's passing a reference to the variable s, which lasts until the end of main().
If it were (e.g. foo(std::move(get_string()));), then the temporary's lifetime lasts until the end of the full expression - after the function has returned. It's therefore quite safe to use it within foo. There is only a danger if foo stores a reference/pointer to its argument, and something else tries to use it later.
There is no need to extend any lifetime here: the object in question lasts until the end of main, which is after the end of foo.
I have this code (simplified version):
const int& function( const int& param )
{
return param;
}
const int& reference = function( 10 );
//use reference
I can't quite decide to which extent C++03 Standard $12.2/5 wording
The temporary to which the reference is bound or the temporary that is the complete object to a subobject of which the temporary is bound persists for the lifetime of the reference...
is applicable here.
Is reference variable in the code above valid or dangling? Will the reference in the calling code prolong the lifetime of the temporary passed as the parameter?
A full-expression is an expression that is not a subexpression of another expression. In this case, the full-expression containing the call function( 10 ) is the assignment expression:
const int& reference = function( 10 );
In order to call function with the argument 10, a temporary const-reference object is created to the temporary integer object 10. The lifetime of the temporary integer and the temporary const-reference extend through the assignment, so although the assignment expression is valid, attempting to use the integer referenced by reference is Undefined Behavior as reference no longer references a live object.
The C++11 Standard, I think, clarifies the situation:
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 parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call.
"The temporary to which the reference is bound ... persists for the lifetime of the reference". In this case, the lifetime of the reference ends at the end of the assignment expression, as does the lifetime of the temporary integer.
This will compile, but you will end up with a dangling reference. param is freed after function returns.
function is called with a reference to the temporary, anonymous object
function returns the reference
now that function has returned the temporary param is released
the reference is now dangling as the object was destroyed
If you had made it non-const, then it wouldn't have compiled because you cannot pass a non-const reference to an anonymous object.
From the C++11 viepoint a reference returned by a function is not a temporary:
12.12.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),
entering a handler (15.3), and in some initializations (8.5).
A function returning a reference dosn't return prvalue ("pure rvalue"), so it's not a temporary.
This seems quite natural: compiler can't manage lifetime of referenced objects, it is programmer's responsibility
Thus, the compiler doesn't provide any liftime guarantes for const int& reference since it is not bounded to temporary.
It is this part that is important
The temporary to which the reference is bound
In this case the parameter is bound to the temporary, and will be destroyed after the call.
You cannot extend the lifetime further by passing the reference on.