why does adding a move constructor disable initializier list? - c++

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);

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:...

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
}

Because you're using aggregate initialization, which says:
Aggregate initialization is a form of list-initialization, which
initializes aggregates An aggregate is one of the following types:
array type class type (typically, struct or union), that has
no private or protected non-static data members
no user-provided,
inherited, or explicit (since C++17) constructors (explicitly
defaulted or deleted constructors are allowed) (since C++11)
no
virtual, private, or protected (since C++17) base classes
no virtual
member functions
Point 2 makes your case fail.

Related

Is it possible to do a two-step initialization of a non-movable object in the member initializer list?

Is it possible to do a two-step initialization of a non-movable object in the member initializer list using C++17?
Here is the legacy API I’m working with (and yes I know it’s bad but I can’t change it)
class A {
public:
A(int some_param);
// no default, move or copy constructor nor assignment
A() = delete
A(const& A) = delete;
A(const&&) = delete; // it shouldn’t be deleted, but I’m not able to modify that class
A& operator= (const& A) = delete;
A& operator= (const&&) = delete;
// and it uses two phase construction
void init(int some_other_argument);
// more stuff
};
class B {
public:
// no default constructor, and must be created with a fully-initialized A
B() = delete;
B(const& A);
// more stuff
};
And here is what I am trying to write:
class C {
public:
C();
private:
A m_a;
B m_b;
};
C::C()
: m_a{ /* two step initialization of A*/}
, m_b(m_a)
{}
The usual trick of using a immediately initialize lambda doesn’t work:
C::C()
: m_a{[] {
auto a = A(0xdead_beef);
a.init(42);
return a; // doesn’t compile because A isn’t movable
}()}
, m_b(m_a)
{}
Nor does using the body of the constructor:
C::C()
// m_a and m_b will be default-initialized here, but their default initializer are deleted
{
m_a = A(0xdead_beef);
m_a.init();
m_b = B(m_a);
}
And nor doing the second step of the two step initialization in the body of the constructor:
C::C()
: m_a(0xdead_beef)
, m_b(m_a) // m_a isn’t fully initialized here
{
m_a.init();
}
Currently I’m using a unique_ptr for m_b, but I was asking myself if there was a better solution.
class C {
public:
C();
private:
std::unique_ptr<A> m_a; // guaranted to be initialized in the constructor, no need to check before dereferencing
B m_b;
};
C::C()
: m_a{[] {
auto a = new A(0xdead_beef);
a->init(42);
return a;
}()}
, m_b(*m_a)
{}
I think the rules of guaranteed move elision were improved in C++20, but I’m still using C++17.
You might still abuse of comma operator:
C::C() : m_a{ 0xdeadbeef },
m_b((m_a.init(42), m_a))
{}

Copy elision when initializing a base-class subobject with aggregate initialization

In the following code, struct B is an aggregate with base struct A, and B-object is aggregate initialized B b{ A{} }:
#include <iostream>
struct A {
A() { std::cout << "A "; }
A(const A&) { std::cout << "Acopy "; }
A(A&&) { std::cout << "Amove "; }
~A() { std::cout << "~A "; }
};
struct B : A { };
int main() {
B b{ A{} };
}
GCC and MSVC perform A copy elision, printing:
A ~A
while Clang creates a temporary and moves it, printing:
A Amove ~A ~A
Demo: https://gcc.godbolt.org/z/nTK76c69v
At the same time, if one defines struct A with deleted move/copy constructor:
struct A {
A() {}
A(const A&) = delete;
A(A&&) = delete;
~A() {}
};
then both Clang (expected) and GCC (not so expected in case of copy elision) refuse to accept it, but MSVC is still fine with it. Demo: https://gcc.godbolt.org/z/GMT6Es1fj
Is copy elision allowed (or even mandatory) here? Which compiler is right?
There is a related question Why isn't RVO applied to base class subobject initialization? posted 4 years ago, but
this question askes about peculiarities of aggregate initialization, not covered there;
as one can see copy elision is applied in the above example by 2 out of 3 tested modern compilers. Is it due to bugs in these compilers (still present after 4 years) or they are allowed doing so?

Copy constructor and assignment operators with polymorphism

Just failed an interview because of this, and I'm still confused:
class A
{
public:
A(int a) : a_(a) {}
// Copy constructor
// Assignment operator
private:
int a_;
};
class B : public A
{
public:
B(int a, int b) : A(a), b_(b) {
}
// Copy constructor
// Assignment operator
private:
int b_;
};
How do you implement the copy constr/assignment op, and why does the constructor of B have to have an A initialized in the initializer list? It doesn't work if it's not in the list.
How do you implement the copy constr/assignment op
Trick question, I think. In this case the best option is to do absolutely nothing. Neither class contains (and owns) any resources requiring more than the default special member functions. Observe the Rule of Zero.
Some style guides recommend explicitly defaulting special member functions. In this case
class A
{
public:
A(int a) : a_(a) {}
A(const A &) = default;
A& operator=(const A &) = default;
private:
int a_;
};
may be appropriate.
why does the constructor of B have to have an A initialized in the initializer list?
All class members and base classes must be fully constructed before entering the body of a constructor.
B(int a, int b)
{ // Entering constructor's body. A must be constructed before we get here.
}
A has no default constructor, so the constructor it does have must be explicitly called in the initializer list to construct the base class before the construction of B can proceed.
Addressing comment
A's copy constructor and assignment operator are trivial:
A(const A & src): a_(src.a_)
{
}
A& operator=(const A & src)
{
a_ = src.a_;
return *this;
}
B is a bit trickier because it also has to make sure A is copied
B(const B & src): A(src), // copy the base class
b_(src.b_)
{
}
B& operator=(const B & src)
{
A::operator=(src); // assign the base class by explicitly calling its
// assignment operator
b_ = src.b_;
return *this;
}
Note: If I were hiring, I'd take the programmer who called me out on the trick question over the programmer who did the extra work and risked an unforced error.
why does the constructor of B have to have an A initialized in the
initializer list?
A only has one constructor which takes an int. B has A as a base class, which means that every B object must first construct an A object. How else are you going to construct that A object with it's required parameter except by using an initialiser list?

How do I set a default value for a class member with something other than its initializer list constructor

Let's say I have something like this:
class A
{
public:
A(int x, int y)
{
std::cout << "Constructed from parameters" << std::endl;
}
// Non-copyable, non-movable
A(A const&) = delete;
A(A&&) = delete;
A& operator=(A const&) = delete;
A& operator=(A&&) = delete;
};
class B
{
public:
B() = default;
private:
A a{1,2};
};
int main()
{
B b;
return 0;
}
This works fine, initializes a with a default value by calling A's constructor, and prints Constructed from parameters.
Then, let's say I want to initialize a differently, so I add a constructor that takes an initializer list and change my code:
class A
{
public:
A(int x, int y)
{
std::cout << "Constructed from parameters" << std::endl;
}
A(std::initializer_list<int> someList)
{
std::cout << "Constructed from an initializer list" << std::endl;
}
// Non-copyable, non-movable
A(A const&) = delete;
A(A&&) = delete;
A& operator=(A const&) = delete;
A& operator=(A&&) = delete;
};
class B
{
public:
B() = default;
private:
A a{1,2,3};
};
int main()
{
B b;
return 0;
}
Everything still works as I want, the code initializes a with a default value by calling A's second constructor and prints Constructed from an initializer list.
Now, let's say I want to keep A as is but go back to setting a's default value through the first constructor. If A was movable, I could use A a = A(1,2);, but that isn't the case here, so how do I go about this? Is it plain impossible to set a default value with that constructor?
Edit: I'm looking for a solution that would work with C++14, but if there is a better solution in C++17 or C++20 that's also something I want to know.
If A was movable, I could use A a = A(1,2);, but that isn't the case
here
Well, for C++17 and later, you can use A a = A(1,2); here, so long as you have that as the declaration/initialisation! The following works (in your second code snippet):
class B {
public:
B() = default;
private:
A a = A( 1, 2 ); // No assignment here - just an initialization.
};
and "Constructed from parameters" is called. This is because there is no actual assignment operation here, just an initialization. However, the following would fail, for the reason you have stated:
class B {
public:
B() { a = A( 1, 2 ); } // error C2280: 'A &A::operator =(A &&)': attempting to reference a deleted function
private:
A a{ 1,2,3 };
};
EDIT: Pre-C++17, the following is a workaround, using the 'old-fashioned' round parentheses, rather than curly braces, in an initializer list in the default constructor for B (tested with C++14 in clang-cl and MSVC):
class B {
public:
B() : a(1,2) {} // Using a{1,2} calls the "initializer list" constructor, however!
private:
A a;
};
I think this is not possible in C++14 without some kind of a "hack". One such hack is to employ an additional disambiguating parameter. For example:
class From_params {};
class A {
public:
A(int, int, From_params = {}) // (1)
{}
A(std::initializer_list<int>) // (2)
{}
...
};
class B {
public:
B() = default;
private:
A a{1, 2, From_params{}}; // calls (1)
};

No Matching Constructor For Initialization

I'm just supposed to be getting used to basic copy constructors.
I assumed I properly placed copy constructors.
But when I try to compile, I keep getting the error "No matching constructor for initialization of B"
I'm a bit confused.
class A {
int valuea;
public:
A(const A&); // copy constructor
int getValuea() const { return valuea; }
void setValuea(int x) { valuea = x; }
};
class B : public A {
int valueb;
public:
B(int valueb);
B(const B&); // copy constructor
int getValueb() const { return valueb; }
void setValueb(int x) { valueb = x; }
};
int main () {
B b1;
b1.setValuea(5);
b1.setValueb(10);
B b2(b1);
cout << "b2.valuea=" << b2.getValuea() << "b2.valueb=" << b2.getValueb() << endl;
return 0;
}
By declaring B(int) and B(const B &), you have disabled the default constructor that is implicitly placed in the class for you when you have no other constructors because for all the compiler knows, you might not want a default constructor, so it can't make assumptions (see here).
Add the following to B, remembering to initialize the base and members with it:
B(){}
In C++11, this works well:
B() = default;
That will allow B to have a default constructor for use when you declare B b1;
The same thing goes for A, too. You have a copy constructor, so there's no longer any default constructor implicitly placed in for you.