According to the C++23 standard, what is the output of this program?
#include <iostream>
#include <utility>
struct A
{
A() { std::cout << "1"; }
A(const A&) { std::cout << "2"; }
A(A&&) { std::cout << "3"; }
};
struct B
{
A a;
B() { std::cout << "4"; }
B(const B& b) : a(b.a) { std::cout << "5"; }
B(B&& b) : a(b.a) { std::cout << "6"; }
};
int main()
{
B b1;
B b2 = std::move(b1);
}When members are not mentioned in the member initializer list, when are they initialized?
Note that the expression b.a, which names a data member of an rvalue reference, is an lvalue here.
1426
First, b1 is default-initialized. All members are initialized before the body of the constructor, so b1.a is default-initialized first, and we get the output 14.
§[class.base.init]¶9 in the standard:
In a non-delegating constructor (...), if a given potentially constructed subobject is not designated by a mem-initializer-id (...), then
— if the entity is a non-static data member that has a default member initializer (§[class.mem]) and (...);
— otherwise, if the entity is an anonymous union or a variant member (§[class.union.anon]), (...);
— otherwise, the entity is default-initialized (§[dcl.init]).
Then, b2 is initialized with the move constructor (since std::move(b1) converts b1 to an xvalue, allowing it to be moved from). In B's move constructor, a is initialized in the member initializer list. Even though b is of type rvalue reference to B (and bound to an rvalue), the expression b.a is an lvalue (§[expr.prim.id.unqual]¶3). Lvalues cannot be moved from, so b2.a is copy initialized, printing 2. Finally, the body of B's move constructor prints 6.
(If the concept of rvalue references being lvalues is confusing, read https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers. Search for "In widget".)