C++ non-static data member initializers, just a little bit confused - c++

I am a little bit confused about why the following code does what it does:
class Base
{
public:
Base() = default;
Base(const Base &) =delete;
Base &operator=(const Base &) = delete;
Base(const char*) {}
};
class Holder
{
public:
Holder() = default;
private:
// Base b = Base();
Base b2 = {};
};
int main()
{
Holder h;
}
in this incarnation, it compiles, however if I un-comment Base b = Base(); it gives the following error:
main.cpp:15:17: error: use of deleted function 'Base::Base(const Base&)'
Base b = Base();
^
main.cpp:5:6: note: declared here
Base(const Base &) =delete;
^
and I am just unable to find in the standard why it tries to call the copy constructor for the Base b = Base() initializer, and why doesn't it call for the Base b2 = {} ... or is this just one of those little obscurities that is hidden in a few words in a paragraph somewhere?
Can you please give a (short) explanation why this happens?
(coliru: http://coliru.stacked-crooked.com/a/c02ba0293eab2ce5 )

That's because, conceptually, that line constructs from Base(), which requires a copy/move constructor. The probable reason why you weren't aware of this, is because that expression generally triggers copy elision: a standard optimization. It's one of those C++ gotcha's.
(31.3) — when a temporary class object that has not been bound to a
reference (12.2) would be copied/moved to a class object with the same
cv-unqualified type, the copy/move operation can be omitted by
constructing the temporary object directly into the target of the
omitted copy/move.
As for why Base b2 = {} works, see
(3.4) — Otherwise, if the initializer list has no elements and T is a
class type with a default constructor, the object is
value-initialized.
You could just do Base b;.

T object = {arg1, arg2, ...}; is syntax for list initialization. There is no copying involved.
T object = T() is not list initialization. The right hand operand constructs a value-initialized temporary, and object is move- or copy-initialized from it. The move and copy can be elided, but the type must be movable or copyable, otherwise this is not allowed.

The true reason is because you forget keyword "explicit", you should change the code
Base(const Base &) =delete;
To:
explicit Base(const Base &) = delete;
Let me explain the reason, In class "Holder", Base b = Base() will:
Base() will generate a temporary object by calling constructor "Base()"
However, constructor "Base(const Base&)" has a single parameter and without keyword "explicit", this will make code:
Base obj = value;
become
Base obj(value);
Therefore, just add keyword "explicit" will solve this problem. ^_^

Related

Deleted move constructor in base class does not stop derived class object to be returned from a function

Given base class A and derived class B, A has deleted move constructor:
class A {
public:
A() {}
A(const A&) = default;
A(A&&) = delete;
};
class B : public A
{
};
In such case, the following function does not compile because of deleted move constructor:
A f() {
A a;
return a;
}
but the similar function for B does not report any error:
B g() {
B b;
return b;
}
Does it mean that move constructor in B is not deleted? I want to know what is the rule in the standard.
The move constructor in B is deleted, but does not participate in overload resolution. According to cppreference:
The implicitly-declared or defaulted move constructor for class T is defined as deleted if any of the following is true:
T has non-static data members that cannot be moved (have deleted, inaccessible, or ambiguous move constructors);
T has direct or virtual base class that cannot be moved (has deleted, inaccessible, or ambiguous move constructors);
T has direct or virtual base class or a non-static data member with a deleted or inaccessible destructor;
T is a union-like class and has a variant member with non-trivial move constructor.
A defaulted move constructor that is deleted is ignored by overload resolution (otherwise it would prevent copy-initialization from rvalue).
The second bullet point applies: B has a direct base class, A, with a deleted move constructor. So B's implicitly-declared move constructor is defined as deleted.
However, when the return statement is evaluating which constructor of B to use, the deleted move constructor is not considered but the valid copy constructor is.
The move constructor of B is deleted, as already answered by #Nathan Pierson.
The reason that you can return the local b from g is, as explained there, that the implicitly deleted move constructor of B is not participating in the overload resolution, thus the compiler selects the default copy constructor of B.
To prove the above take a look at the following code, adding a moveable only member into B:
class B : public A {
std::unique_ptr<int> ptr;
public:
B() {}
// needs this to compile:
// B(B&& b): A(b), ptr(std::move(b.ptr)) {}
};
B g() {
B b;
// now this would fail
// B doesn't have a default move ctor
// (it is implicitly deleted because of A)
// and the default copy ctor is not valid
return b;
}
Hisenter link description here
enter link description here

Implicit move constructor

What is exactly the implicit move constructor doing? For example how would the implicit move constructor look like for the following class (could you provide some example implementation of this implicit constructor):
struct A
{
A() = default;
A(A && other) = default;
int a;
};
struct B : public A
{
int b;
int * c;
};
Would the implementation look like this:
B(B && other) : A(std::move(other)), b(std::move(other.b)), c(std::move(other.c)) {}
From cppreference.com:
For union types, the implicitly-defined move constructor copies the
object representation (as by std::memmove). For non-union class types
(class and struct), the move constructor performs full member-wise
move of the object's bases and non-static members, in their
initialization order, using direct initialization with an xvalue
argument. If this satisfies the requirements of a constexpr
constructor, the generated move constructor is constexpr.
The base class constructors runs before the derived one.

C++ Default constructor not inherited with "using" when move and copy constructors present

class A{
public:
A(){};
};
class B : public A{
public:
using A::A;
B(const B&) = default;
B( B&&) = default;
};
B b;
The compiler (g++ (5.4.0-6ubuntu1) / c++11) says "no matching function for call to B::B()" and lists the copy and move constructors as candidates. If I comment those defaulted ones out then it compiles. What causes this? And what difference does it make that they are explicitly defaulted? If those 2 lines weren't there they would be defaulted anyway.
Before C++17, the default constructor of the base class won't be inherited via using:
All candidate inherited constructors that aren't the default constructor or the copy/move constructor and whose signatures do not match user-defined constructors in the derived class, are implicitly declared in the derived class. (until C++17)
After C++17 the code works fine.
Before that, the default constructor won't be inherited from the base class, and won't be generated for class B because copy/move constructor are provided.
If no user-declared constructors of any kind are provided for a class type (struct, class, or union), the compiler will always declare a default constructor as an inline public member of its class.
That's why if you comment copy/move constructor out it compiles. You can add the definition explicitly as a pre-C++17 workaround. e.g.
class B : public A {
public:
B(const B&) = default;
B( B&&) = default;
B() = default;
};
The code compiles with gcc8.
If you declare any constructors, the default constructor is not implicitly generated, you can generate it by adding a = default for it as well:
class B : public A {
public:
B() = default;
B(const B&) = default;
B( B&&) = default;
};
This has changed with C++17 (as pointed out by other answer).
The default constructor cannot be inherited, the standard explicitly says so. Quoting C++11 12.9 [class.inhctor]/3 (emphasis mine) (*):
For each non-template constructor in the candidate set of inherited constructors other than a constructor
having no parameters or a copy/move constructor having a single parameter, a constructor is implicitly
declared with the same constructor characteristics unless there is a user-declared constructor with the same
signature in the class where the using-declaration appears. ...
This means that for the default constructor, normal rules apply as if the using A::A; declaration wasn't there. So the presence of any other constructor declaration (such as the copy & move constructor) causes the default constructor not to be implicitly declared. Note that you can easily add it back by explicitly defaulting it:
class B : public A{
public:
using A::A;
B() = default;
B(const B&) = default;
B( B&&) = default;
};
(*) The same wording is present in C++14 (n4140), at the same location. I can't seem to find equivalent wording in C++1z (looking through n4582)

Unreasonably deleted move constructor

I have a class with explicitely deleted copy constructor named, let's say, NonCopyable. Then there's a class Base1 with a member of type NonCopyable. And another class Base2 with Base1 as parent. And finally - a Derivative class which parent is Base2.
Since NonCopyable is non-copyable, it's obvious that Base1, Base2 and Derivative will be also non-copyable. But it seems like there's been also move constructor (and assign operator) deleted.
Test it here:
GCC
MSVC
Following line gives those errors:
Derived d1, d2 = std::move(d1);
GCC: 'Derived::Derived(Derived&&)' is implicitly deleted because the default definition would be ill-formed:
class Derived :
the rest of errors just claims that Base1 and Base2 copy ctors are implicitly deleted which is not weird
MSVC: error C2280: 'Derived::Derived(const Derived &)': attempting to reference a deleted function
In provided links there's also commented lines which gives similar errors. Please uncomment them to see more errors I'd like to show you (e.g. with remove_if uncommented it complains about deleted move (on GCC) /copy (on MSVC) assign operator).
What I want to achieve is make Derivative (and its bases also) moveable, but not copyable.
I'm working with Visual Studio 2015 and getting exactly the same error as in the msvc link I provided, but I'm not sure what version of MSVC is used to compile on rextester.com.
I'm pasting the code also here at the request of #Richard Critten:
class NonCopyable
{
public:
NonCopyable() { }
NonCopyable(const NonCopyable &) = delete;
NonCopyable & operator=(const NonCopyable &) = delete;
NonCopyable(NonCopyable &&) { }
NonCopyable & operator=(NonCopyable &&) { return *this; }
};
class Base1
{
public:
virtual ~Base1() = default;
private:
NonCopyable m;
};
class Base2 :
public Base1
{
public:
virtual ~Base2() = default;
};
class Derived :
public Base2
{
};
int main()
{
std::vector<Derived> v;
//std::remove_if(v.begin(), v.end(), [](const Derived &) { return true; });
//v.emplace_back();
Derived d1, d2 = std::move(d1);
}
[class.copy]/9 If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if
(9.4) — X does not have a user-declared destructor.
Base1 has a user-declared destructor, so no move constructor for it. And copy constructor is implicitly declared as deleted due to non-copyable member. So Base1 can be neither copied nor moved, and of course Base2 and Derived together with it.

Constructor confusion

I always think I know C++ pretty well, but sometimes I'm surprised by even the most fundamental things.
In the following scenario, I'm confused as to why the constructor Derived::Derived(const Base&) is invoked:
class Base
{ };
class Derived : public Base
{
public:
Derived() { }
Derived(const Base& b)
{
std::cout << "Called Derived::Derived(const Base& b)" << std::endl;
}
};
int main()
{
Derived d;
Base b;
d = b;
}
This outputs: Called Derived::Derived(const Base& b), indicating that the second constructor in Derived was invoked. Now, I thought I knew C++ pretty well, but I can't figure out why that constructor would be invoked. I understand the whole "rule of four" concept, and I would think that the expression d = b would do one of two things: Either it would 1) invoke the implicit (compiler-generated) assignment operator of Base, or 2) Trigger a compiler error complaining that the function Derived& operator = (const Base&) does not exist.
Instead, it called a constructor, even though the expression d = b is an assignment expression.
So why does this happen?
d = b can happen because b is converted to Derived.
The second constructor is used for automatic type conversion.
It's like d = (Derived) b
Derived isa Base, but Base isn'ta Derived, so it has to be converted before assignment.
assigning base to derived? perhaps you meant (a) by ref (b) or derived to base. This doesn't really make sense, but the compiler is correctly using your (non-explicit) constructor to convert the Base instance to a new Derived instance (which is subsequently assigned into d).
Use an explicut constructor to prevent this from happening automatically.
Personally I think you messed up your code sample, because, normally assigning firstclass base to derived makes no sense without a conversion
There are two interacting features at play here:
Assignment Operators are never inherited
A constructor that is not explicit, or a conversion operator (operator T()) define a user-conversion that can be used implicitly as part of a conversion sequence
Assignement Operators are never inherited
A simple code example:
struct Base {}; // implicitly declares operator=(Base const&);
struct Derived: Base {}; // implicitly declares operator=(Derived const&);
int main() {
Derived d;
Base b;
d = b; // fails
}
From ideone:
prog.cpp: In function ‘int main()’:
prog.cpp:7: error: no match for ‘operator=’ in ‘d = b’
prog.cpp:2: note: candidates are: Derived& Derived::operator=(const Derived&)
Conversion sequence
Whenever there is an "impedance" mismatch, such as here:
Derived::operator= expects a Derived const& argument
a Base& is provided
the compiler will try to establish a conversion sequence to bridge the gap. Such a conversion sequence may contain at most one user-defined conversion.
Here, it will look for:
any constructor of Derived that can be invoked with a Base& (not explicit)
a conversion operator in Base that would yield a Derived item
There is no Base::operator Derived() but there is a Derived::Derived(Base const&) constructor.
Therefore our conversion sequence is defined for us:
Base&
Base const& (trivial)
Derived (using Derived::Derived(Base const&))
Derived const& (temporary object bound to a const reference)
And then Derived::operator(Derived const&) is called.
In action
If we augment the code with some more traces, we can see it in action.
#include <iostream>
struct Base {}; // implicitly declares Base& operator(Base const&);
struct Derived: Base {
Derived() {}
Derived(Base const&) { std::cout << "Derived::Derived(Base const&)\n"; }
Derived& operator=(Derived const&) {
std::cout << "Derived::operator=(Derived const&)\n";
return *this;
}
};
int main() {
Derived d;
Base b;
d = b;
}
Which outputs:
Derived::Derived(Base const&)
Derived::operator=(Derived const&)
Note: Preventing this ?
It is possible, in C++, to remove a constructor for being used in conversion sequences. To do so, one need to prefix the declaration of the constructor using the explicit keyword.
In C++0x, it becomes possible to use this keyword on conversion operators (operator T()) as well.
If here we use explicit before Derived::Derived(Base const&) then the code becomes ill-formed and should be rejected by the compiler.
Since you've defined a constructor for Derived which takes type Base and you are down-casting Base, the compiler chooses the most suitable constructor for the upcast, which in this case is the Dervied(const Base& b) you've defined. If you did not define this constructor you would actually get a compiling error when trying to make the assignment. For more info, you can read the following at Linuxtopia.
It can't assign value of different type, so it should first construct a Derived temporary.