A friend of mine showed me a program in C++20:
#include <iostream>
struct A
{
A() {std::cout << "A()\n";}
~A() {std::cout << "~A()\n";}
};
struct B
{
const A &a;
};
int main()
{
B x({});
std::cout << "---\n";
B y{{}};
std::cout << "---\n";
B z{A{}};
std::cout << "---\n";
}
In GCC it prints:
A()
~A()
---
A()
---
A()
---
~A()
~A()
https://gcc.godbolt.org/z/ce3M3dPeo
So the lifetime of A
is prolonged in cases y and z.
In Visual Studio the result is different:
A()
~A()
---
A()
---
A()
~A()
---
~A()
So the lifetime of A
is only prolonged in case y.
Could you please explain why the type of braces influences the object lifetime?
Gcc is correct. The lifetime of the temporary will be extended only when using list-initialization syntax (i.e. using braces) in initialization of an aggregate.
(since C++20) a temporary bound to a reference in a reference element of an aggregate initialized using direct-initialization syntax (parentheses) as opposed to list-initialization syntax (braces) exists until the end of the full expression containing the initializer.
struct A { int&& r; }; A a1{7}; // OK, lifetime is extended A a2(7); // well-formed, but dangling reference
(emphasis mine)
otherwise, if the destination type is a (possibly cv-qualified) aggregate class, it is initialized as described in aggregate initialization except that narrowing conversions are permitted, designated initializers are not allowed, a temporary bound to a reference does not have its lifetime extended, there is no brace elision, and any elements without an initializer are value-initialized. (since C++20)