16

I'm trying to understand lifetime extension guarantees in C++. Can someone explain why the usage of different types of parentheses below yields differing results in terms of when the temporary object destructor is invoked?

#include <iostream>
struct X  {
    X() { 
        std::cout << __PRETTY_FUNCTION__ <<"\n";
    }
    ~X() {
        std::cout << __PRETTY_FUNCTION__ <<"\n";
    }
};

struct Y {
    X &&y;
};
int main() { 
    Y y1(X{});    
    std::cout << "Here1\n";
    Y y2{X{}};
    std::cout << "Here2\n";
}

Output

X::X()
X::~X()
Here1
X::X()
Here2
X::~X()
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
user3882729
  • 1,339
  • 8
  • 11

1 Answers1

18

Since C++20:

There are following exceptions to this lifetime rule:

  • ...

  • a temporary bound to a reference in a reference element of an aggregate initialized using direct-initialization syntax (parentheses) exists until the end of the full expression containing the initializer, as opposed to list-initialization syntax {braces}.

    struct A
    {
        int&& r;
    };
    
    A a1{7}; // OK, lifetime is extended
    A a2(7); // well-formed, but dangling reference
    

That means for Y y2{X{}};, the lifetime of temporary will be extended as y2 (and its member); while for Y y1(X{}); it won't, the temporary will be destroyed after full expression immediately.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • 13
    I have been a C++ programmer for nearly 15 years now, and I cannot fully explain why there are so many initialization syntaxes with extremely subtle differences. What even. – Silvio Mayolo Apr 24 '23 at 02:39
  • 3
    @SilvioMayolo Even people working with C++ longer than 15 years struggle with understanding the differences. The reasons are largely historical, given that the language has evolved while retaining backward compatibility. Some syntax/semantics are retained from C, some others from early C++ standards (and preceding draft standards). C++11 introduced syntax/semantics to help address frustrations (e.g. the most vexing parse) and also to better support new language features (such as rvalue references, etc). C++17 and later tweaked further ... – Peter Apr 24 '23 at 05:25
  • Well, yet another reason why braced initialization should be preferred over others. – digito_evo Apr 24 '23 at 05:25