Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why does adding a move constructor disable initializier list?

With a simple struct such as

struct Foo { int i; };

I can create a new instance using an initializer list; no need to write a constructor:

Foo foo { 314 };

If I 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'm guessing this behavior is somewhat related to this answer (for user-defined move-constructor disables the implicit copy-constructor?), but more details would be nice.

Edit: as indicated by the answers, this has to do with adding a constructor. Which in turn would seem to create an inconsistency of sorts, if I add just a 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 actually default construct and move assignment; but the end result seems about the same):

Baz baz{ 3141 };
Baz b;
b = std::move(baz);
like image 929
Ðаn Avatar asked Nov 25 '16 15:11

Ðаn


People also ask

What does the move constructor do?

A move constructor enables the resources owned by an rvalue object to be moved into an lvalue without copying.

Is move constructor automatically generated?

No move constructor is automatically generated.

How do I turn off move constructor?

To correct this, remove the move constructor completely. In the case of the class, once a copy constructor is present (user defined), the move is implicitly not generated anyway (move constructor and move assignment operator).

Does STD move move constructor?

std::move is actually just a request to move and if the type of the object has not a move constructor/assign-operator defined or generated the move operation will fall back to a copy.


2 Answers

When there are no constructors this syntax is aggregate initialization because 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 list initialization, the relevant ones are:

The effects of list initialization of an object of type T are:

  • Otherwise, if T is an aggregate type, aggregate initialization is performed.
  • Otherwise, the constructors of T are considered, in two phases:...
like image 139
Maxim Egorushkin Avatar answered Oct 11 '22 13:10

Maxim Egorushkin


It is not initializer list construction that is disabled by the move constructor (that was not present there to start with), but aggregate construction. And for a good reason: by adding a custom constructor, we indicate to the compiler exactly that the class in not an aggregate, that something different is necessary than just operate on each of its members one by one.

For an aggregate, the default default, copy, and move constructor will work well even if the member variables have nontrivial types. A copy construction will automatically be deleted if it can't be delegated to them, leaving move construction usable:

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 delete copy construction for some other reason and keep the others at default, just be explicit about it:

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
}
like image 45
The Vee Avatar answered Oct 11 '22 13:10

The Vee