Why does adding a move constructor disable the initializer list?

Using a simple struct like

 struct Foo { int i; }; 

I can create a new instance using the list of initializers; no need to write a constructor:

 Foo foo { 314 }; 

If you now add a move constructor

 struct Bar { int i; Bar(Bar&& other) { i = other.i; } }; 

The initializer no longer works, and I have to add a constructor too:

 Bar(int i) : i(i) {} 

I assume that this behavior is somewhat related to this answer (for user-defined move-constructor disables implicit copy-constructor? ), But more detailed information would be nice.

Edit: as indicated in the answers, this has to do with adding a constructor. Which, in turn, seems to create inconsistency if I add only the move operator:

 struct Baz { int i; Baz& operator=(Baz&& other) { this->i = other.i; return *this; } }; 

The initializer works again, although with a slightly different syntax for "move" (yes, this is the actual default construct and the transfer destination, but the end result seems to be pretty much the same):

 Baz baz{ 3141 }; Baz b; b = std::move(baz); 
+5
source share
3 answers

This is not an initialization list construct that is disabled by the move constructor (which was not there), but an aggregate construct. And for a good reason: adding a custom constructor, we tell the compiler exactly that the class in is not an aggregate , that something else is required than just working with each of its members one at a time.

For an aggregate, the default constructor, copying, and moving by default will work well, even if member variables are of non-trivial types. The copy construct will be automatically deleted if it cannot be delegated by it, while leaving the move construct:

 struct A { // non-copyable int a; int b; A(int a_, int b_): a(a_), b(b_) { std::cout << "A(int,int)\n"; } A() { std::cout << "A()\n"; } A(const A&) = delete; A(A&&) { std::cout << "A(A&&)\n"; } }; struct B { A a; }; int main() { B b1{{1,2}}; // OK: aggregate B b2{std::move(b1)}; // OK: calls A::A(A&&) //B b3{b1}; // error: B::B(const B&) auto-deleted } 

However, if you want to remove the copy build for some other reason and keep the rest by default, just specify this:

 struct A { // copyable int a; int b; A(int a_, int b_): a(a_), b(b_) { std::cout << "A(int,int)\n"; } A() { std::cout << "A()\n"; } A(const A&) { std::cout << "A(const A&)\n"; } A(A&&) { std::cout << "A(A&&)\n"; } }; struct B { // non-copyable A a; B() = default; B(const B&) = delete; B(B&&) = default; }; int main() { B b1{{1,2}}; // OK: still an aggregate B b2{std::move(b1)}; // delegates to A::A(A&&) //B b3{b1}; // error } 
+3
source

If there are no constructors, this syntax is aggregate initialization , since this structure is an aggregate.

When a constructor is added, this structure is no longer an aggregate; aggregate initialization cannot be used. The exact rules are listed in the initialization list , the following are relevant:

Effects of initializing a list of an object of type T:

  • Otherwise, if T is an aggregate type, aggregate initialization is performed.
  • Otherwise, the constructors T are considered in two phases: ...
+6
source

Since you are using aggregate initialization , which says:

Aggregate initialization is a form of list initialization that initializes aggregates. An aggregate is one of the following types: the type of an array type of type (typically a struct or union) that has

  • no private or protected non-static data elements
  • not provided by the user of inherited or explicit (starting from C ++ 17) constructors (default or remote constructors are explicitly allowed) (starting from C ++ 11)
  • no virtual, private or protected (starting from C ++ 17) base classes
  • no virtual member functions

Point 2 causes your case to fail.

+3
source

Source: https://habr.com/ru/post/1260360/


All Articles