I am having trouble understanding why the lifetime of temporary referenced constant reference parameters is shortened when there is an ideal redirect constructor. First, what do we know about time references to reference parameters: they are saved for full expression:
The time reference to the reference parameter in the function call (5.2.2) is maintained until the completion of the full expression containing the call
However, I found cases where this is incorrect (or I just donβt understand what the full expression is). Let's take a simple example: first we define an object with detailed constructors and destructors:
struct A { A(int &&) { cout << "create A" << endl; } A(A&&) { cout << "move A" << endl; } ~A(){ cout << "kill A" << endl; } };
And the wrapper of object B that will be used to collapse the link:
template <class T> struct B { T value; B() : value() { cout << "new B" << endl; } B(const T &__a) : value(__a) { cout << "create B" << endl; } B(const B &p) = default; B(B && o) = default; ~B(){ cout << "kill B" << endl; }; };
Now we can use our wrapper to capture links on temporary pages and use them in function calls, for example:
void foo(B<const A&> a){ cout << "Using A" << endl; } int main(){ foo( {123} ); }
The program above prints what I expect:
create A create B Using A kill B kill A
So far so good. Now back to B and add the ideal call divert constructor for convertible types:
template <class T> struct B { template <class U, class = typename enable_if<is_convertible<U, T>::value>::type> B(U &&v) : value(std::forward<U>(v)) { cout << "new forward initialized B" << endl; } };
Compiling the same code again gives:
create A new forward initialized B kill A Using A kill B
Note that our object A now been killed before its use, which is bad! Why, in this case, the temporary service life does not extend to a full call to foo ? In addition, there is no other call to handle A , so there is no other instance.
I see two possible explanations:
- either the types are not what I think they are: changing the constructor of the moveable move to
B(T &&v) instead of template <class U>B(U &&v) solves the problem. - or
{123} not a subexpression of foo( {123} ) . The permutation {123} for A(123) also solves the problem, which makes me wonder if the parenthesis initializers are complete expressions.
Can anyone clarify what is going on here?
Does this mean that adding a transfer constructor to the class can lead to incorrect compatibility in some cases, for example, for B ?
Here you can find the full code, and another test case crashes for string references.