Home > Software engineering >  GCC vs. Clang on the lifetime of temporary bound to an rvalue reference of another temporary
GCC vs. Clang on the lifetime of temporary bound to an rvalue reference of another temporary

Time:08-10

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
  • Related