I want to figure out the lifetime of a temporary object S{}
bound to an rvalue reference inside struct wrap<T>
.
wrap<T>::f()
is a function that potentially interacts with the temporary; therefore, S{}
must be alive when wrap<T>::f()
is called.
I consider two cases: (1) wrap{S{}}.f();
and (2) auto w = wrap{S{}}; w.f();
.
I am confident the first case should not result in any dangling references because S{}
is destroyed only after the entire expression is evaluated.
However, for the second case, I am not really certain.
I hypothesized that the lifetime of the temporary should match the lifetime of w
.
To test my hypothesis, I wrote the following code snippet (also available on Compiler Explorer):
#include <stdio.h>
struct S {
S() { puts("S::S()"); }
~S() { puts("S::~S()"); }
};
template <typename T>
struct wrap {
T &&t;
void f() {
puts("wrap<T>::f()");
}
};
template <typename T>
wrap(T &&) -> wrap<T>;
int main() {
#if defined(_MSC_VER)
puts("msvc");
#elif defined(__clang__)
puts("clang");
#else // gnu
puts("gcc");
#endif
puts("== first ==");
wrap{S{}}.f();
puts("== second ==");
auto w = wrap{S{}};
w.f();
return 0;
}
It seems like there is a disagreement among the compilers:
For x86-64 GCC 12.1
with -std=c 20
:
gcc
== first ==
S::S()
wrap<T>::f()
S::~S()
== second ==
S::S()
wrap<T>::f()
S::~S()
For x86-64 clang (trunk)
with -std=c 20
:
clang
== first ==
S::S()
wrap<T>::f()
S::~S()
== second ==
S::S()
S::~S()
wrap<T>::f()
And, finally, x64 msvc v19.latest
with /std:c latest
:
msvc
== first ==
S::S()
wrap<T>::f()
S::~S()
== second ==
S::S()
wrap<T>::f()
S::~S()
GCC and MSVC extend the lifetime of the temporary object, whereas clang does not. Which behavior is correct according to the standard?
CodePudding user response:
clang is incorrect: both examples are valid and within lifetimes.
The rule is that temporaries bound to references live as long as the reference (with some exceptions that don't apply here). Note that:
auto w = wrap{S{}};
is exactly equivalent to:
wrap<S> w{S{}};
And list-initialization directly binds (as opposed to parentheses).
There is even an example in the standard that is basically exactly this, in [class.temporary]:
struct S {
const int& m;
};
const S& s = S{1}; // both S and int temporaries have lifetime of s