In C++, we can create temporary values, and these temporary values have a lifespan.
From cppreference.com:
All temporary objects are destroyed as the last step in evaluating the full-expression that (lexically) contains the point where they were created, and if multiple temporary objects were created, they are destroyed in the order opposite to the order of creation. ...
...
- 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.
An expression could be written such that the resulting object will have dependent rvalue references. One could remove those dependencies by allocating a non-reference object and moving the temporary's contents into it, but that would be less efficient than just using the temporaries, due to the additional moves/copies.
By inserting such an expression with dependent temporary object(s) in as a function parameter, this will result in the function receiving a valid object. This would be because the expression has become a sub-expression of the full-expression.
However, if one were to extend the life of the object which is created by this same expression, the expression has now become the full-expression, so I would have expected the temporaries to live on with the final temporary in the worst case or just the dependent ones to in the best case. However, it appears that all of the intermediate temporaries are just being destroyed, resulting in a temporary with an extended lifespan with dangling references/pointers.
I believe that this issue is going to become even more relivant now that we have rvalue references available to us rather than just const references.
So my question is, why is this so? Was no use case for extending the life of dependent rvalues though of? Or was there deliberate thought behind this?
Here is an example of what I mean:
#include <iostream>
struct Y
{
Y() { std::cout << " Y construct\n"; }
~Y() { std::cout << " Y destruct\n"; }
};
struct X
{
Y&& y;
X(Y&& y)
: y( (std::cout << " X construct\n",
std::move(y)) ) {}
~X() { std::cout << " X destruct\n"; }
operator Y&() { return y; }
};
void use(Y& y)
{
std::cout << " use\n";
}
int main()
{
std::cout << "used within fn call\n";
use(X(Y()));
std::cout << "\nused via life extention\n";
auto&& x = X(Y());
use(x);
}
Output:
used within fn call Y construct X construct use X destruct Y destruct used via life extention Y construct X construct Y destruct use X destruct