Move constructor bypasses copy constructor - c++

As expected the following does not compile:
class A {
public:
A() = default;
//A(A&&) = default;
A(const A&) = delete;
int x;
};
int main()
{
auto a4 = A{}; // not ok, copy constructor is deleted
auto a5 = A(); // not ok, copy constructor is deleted
return 0;
}
But if a move constructor is added, even with the copy constructor explicitly deleted, then the following does compile:
class A {
public:
A() = default;
A(A&&) = default;
A(const A&) = delete;
int x;
};
int main()
{
auto a4 = A{}; // now ok, even though copy constructor is deleted
auto a5 = A(); // now ok, even though copy constructor is deleted
return 0;
}
Why isn't the deleted copy constructor considered?

Why isn't the deleted copy constructor considered?
It is considered. It's just not used, so the fact that it's deleted doesn't matter. That rule, from [dcl.fct.def.delete] is:
A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed.
[ Note: [...] If a function
is overloaded, it is referenced only if the function is selected by overload resolution. [...] —end note ]
Overload resolution on auto a = A{}; (braces vs parens is equivalent in this case) finds two constructor candidates:
A(const A&);
A(A&& );
One of the rules for choosing which candidate is the "best viable" candidate is, from [over.match.best]:
Given these definitions, a viable function F1 is defined to be a better function than another viable function
F2 if [...], and then
— [...]
— the context is an initialization by conversion function for direct reference binding (13.3.1.6) of a reference
to function type, the return type of F1 is the same kind of reference (i.e. lvalue or rvalue) as the
reference being initialized, and the return type of F2 is not
The move constructor is the same kind of reference (rvalue) as the parameter, whereas the copy constructor is not. Hence it's preferred and selected as the best viable candidate. Since A(const A&) wasn't selected by overload resolution, we're not referring to that constructor, so the code is fine.
If we actually used the copy constructor on the other hand (e.g. A a(a5)), that would actually attempt to use the copy constructor, which would be ill-formed.

Related

How is overload resolution performed in the case of an implicitly-deleted move constructor?

As far as I know, when we have a set of overloaded functions, first the overload resolution is being performed, and only after this the access specifier is checked (such as =delete), and not the other way around. In other words, the code below
void f(int);
void f(double) = delete;
int main()
{
f(42.2); // error, use of deleted function
}
Live on coliru
produces an error, since f(double) is the best match and is deleted, and the compiler doesn't "fall back" on f(int).
I also know that marking a destructor as =delete will implicitly delete the move constructor. So, I'd expect the following code
struct Foo
{
Foo() = default;
Foo(const Foo&) = default;
~Foo() = default; // implicitly deletes the move ctor
};
int main()
{
// Why doesn't the implicitly deleted move ctor
// participate in overload resolution?
Foo mfoo = Foo{};
}
Live on Coliru
to fail, as the move constructor is deleted, but instead the compiler "falls through to copy constructor". Is this an exception in the C++ overload resolution process?

Why must thrown objects be copy-initialized?

Exceptions use the statical type of an object to copy-initialize the thrown object. For instance:
struct foo
{
foo() = default;
foo(const foo&) = delete;
};
int main()
{
throw foo();
}
Clang++ --std=c++14 complains that the explicitly-deleted copy constructor can't be used. Why can't it be move-initialized instead?
It can't be move constructed because the type has no move constructor. A deleted copy constructor suppresses the implicit move constructor.
Modify the code to the following:
struct foo
{
foo() = default;
foo(const foo&) = delete;
foo(foo&&) = default;
};
int main()
{
throw foo();
}
Read this, the section "Implicitly-declared move constructor".
Because foo(foo&& ); is missing. By deleteing the copy constructor you've supressed move constructor as well.
The applicable phrasing from the standard (§[class.copy]/9) looks roughly like this (well, exactly like this, as of N4296):
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:
X does not have a user-declared copy ctor,
[...]
This applies because defining the copy ctor as deleted still means you've declared the copy ctor.

Why does `std::pair<int, movable>` require a [deleted] `const&` copy constructor?

I'm busy testing an implementation of various generic algorithms and I'm using types with minimal support of provided functions. I came across this weird setup when using a std::pair<T, movable> with some type T (e.g., int) and a movable type defined like this:
struct movable
{
movable() {}
movable(movable&&) = default;
// movable(movable const&) = delete;
movable(movable&) = delete;
};
The idea is have a type which is movable but not copyable. That works great, e.g., with expressions like this:
movable m1 = movable();
movable m2 = std::move(m1);
However, when trying to use this type as a member of std::pair<...> it fails! To make get the code to compile it is necessary to add the deleted(!) copy constructor taking a movable const& (or have only that version). The copy constructor taking a non-const reference is insufficient:
#include <utility>
auto f() -> std::pair<int, movable> {
return std::pair<int, movable>(int(), movable());
}
What is going on here? Is std::pair<...> overspecified by mandating that std::pair(std::pair const&) is = defaulted?
The problem seems to be down to the specification of std::pair's copy constructor (in 20.3.2 [pairs.pair] synopsis):
namespace std {
template <class T1, class T2>
struct pair {
...
pair(const pair&) = default;
...
};
}
A quick check with my implementation implies that the obvious implementation copying the two members does not require the const& version of the movable copy constructor. That is, the offensive part is the = default on pair's copy constructor!
std::pair copy constructor is declared as follows:
pair(const pair&) = default;
By declaring this copy constructor for movable:
movable(movable&) = delete;
you inhibit implicit creation of movable(const movable&) (so it's not even deleted, there's just no such constructor), thus this is the only copy constructor you have. But std::pair copy constructor requires a copy constructor of its members to take const reference, so you get compile error.
If you add this:
movable(movable const&) = delete;
or (better) just remove movable(movable&) = delete; declaration, you now have the movable(movable const&) constructor, and because it's deleted, the std::pair copy constructor also becomes deleted.
Update: Let's consider a simpler example demonstrating the same issue. This doesn't compile:
template <typename T>
struct holder {
T t;
// will compile if you comment the next line
holder(holder const&) = default;
// adding or removing move constructor changes nothing WRT compile errors
// holder(holder&&) = default;
};
struct movable {
movable() {}
movable(movable&&) = default;
// will also compile if you uncomment the next line
//movable(movable const&) = delete;
movable(movable&) = delete;
};
holder<movable> h{movable()};
It will compile if you comment the copy constructor of holder, because this is how implicit copy constructor generation works ([class.copy]/8:
The implicitly-declared copy constructor for a class X will have the form
X::X(const X&)
if each potentially constructed subobject of a class type M (or array thereof) has a copy constructor whose first parameter is of type const M& or const volatile M&. Otherwise, the implicitly-declared copy constructor will have the form
X::X(X&)
That is, when you comment out the declaration holder(holder const&) = default; the implicitly declared copy constructor of holder will have the form holder(holder&). But if you don't, T's copy constructor has take const T& (or const volatile T&) because this is what will be called in memberwise copy procedure described in [class.copy]/15.
And if holder has a move constructor, it's even easier - if you comment out holder(holder const&) = default;, the implicitly declared copy constructor of holder will be just deleted.

The case when the copy-constructor implicitly defined as deleted

The section N3797::12.8/11 [class.copy] says:
An implicitly-declared copy/move constructor is an inline public
member of its class. A defaulted copy/ move constructor for a class X
is defined as deleted (8.4.3) if X has:
[...]
— a non-static data
member of class type M (or array thereof) that cannot be copied/moved
because overload resolution (13.3), as applied to M’s corresponding
constructor, results in an ambiguity or a function that is deleted or
inaccessible from the defaulted constructor
The first case about the ambiguity of corresponding copy/move constructor is quite clear. We can write the following:
#include <iostream>
using namespace std;
struct A
{
A(){ }
A(volatile A&){ }
A(const A&, int a = 6){ }
};
struct U
{
U(){ };
A a;
};
U u;
U t = u;
int main(){ }
to reflect that. But what about or a function that is deleted or inaccessible from the defaulted constructor? What's that got with a function inaccessible from the default constructor? Could you provide an example reflecting that?
Simply put:
struct M { M(M const&) =delete; };
struct X { X(X const&) =default; M m; }; // X(X const&) is actually deleted!
Implicitly-declared functions are also considered "defaulted" ([dcl.fct.def.default] / 5); a more familiar pre-C++11 example might be something like:
struct M { protected: M(M const&); };
struct X { M m; }; // X's implicit copy constructor is deleted!
Note that if you explicitly default the function after it has been declared, the program is ill-formed if the function would be implicitly deleted ([dcl.fct.def.default] / 5):
struct M { M(M const&) =delete; };
struct X { X(X const&); M m; };
X::X(X const&) =default; // Not allowed.
a non-static data member of class type M (or array thereof) that cannot be copied/moved because overload resolution (13.3), as applied to M’s corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor
The wording is perhaps slightly contrived, for conciseness most certainly. The idea, as emphasised in the above, is that the function in question is the M copy constructor being overloaded in way which renders it inaccessible. So having a member of class M whose copy constructor is made protected for instance would delete the copy constructor of X. Likewise, simply deleting the copy constructor of M would have the same result.

How is a default move constructor for a class with STL members defined? [duplicate]

Is this
struct Example {
string a, b;
Example(Example&& mE) : a{move(mE.a)}, b{move(mE.b)} { }
Example& operator=(Example&& mE) { a = move(mE.a); b = move(mE.b); return *this; }
}
equivalent to this
struct Example {
string a, b;
Example(Example&& mE) = default;
Example& operator=(Example&& mE) = default;
}
?
Yes both are the same.
But
struct Example {
string a, b;
Example(Example&& mE) = default;
Example& operator=(Example&& mE) = default;
}
This version will permits you to skip the body definition.
However, you have to follow some rules when you declare explicitly-defaulted-functions :
8.4.2 Explicitly-defaulted functions [dcl.fct.def.default]
A function definition of the form:
attribute-specifier-seqopt decl-specifier-seqopt declarator virt-specifier-seqopt = default ;
is called an explicitly-defaulted definition. A function that is explicitly defaulted shall
be a special member function,
have the same declared function type (except for possibly differing ref-qualifiers and except that in the case of a copy constructor or copy assignment operator, the parameter type may be “reference to non-const T”, where T is the name of the member function’s class) as if it had been implicitly declared,
not have default arguments.
Is a =default move constructor equivalent to a member-wise move constructor?
Yes. Update: Well, not always. Look at this example:
#include <iostream>
struct nonmovable
{
nonmovable() = default;
nonmovable(const nonmovable &) = default;
nonmovable( nonmovable &&) = delete;
};
struct movable
{
movable() = default;
movable(const movable &) { std::cerr << "copy" << std::endl; }
movable( movable &&) { std::cerr << "move" << std::endl; }
};
struct has_nonmovable
{
movable a;
nonmovable b;
has_nonmovable() = default;
has_nonmovable(const has_nonmovable &) = default;
has_nonmovable( has_nonmovable &&) = default;
};
int main()
{
has_nonmovable c;
has_nonmovable d(std::move(c)); // prints copy
}
It prints:
copy
http://coliru.stacked-crooked.com/a/62c0a0aaec15b0eb
You declared defaulted move constructor, but copying happens instead of moving. Why? Because if a class has even a single non-movable member then the explicitly defaulted move constructor is implicitly deleted (such a pun). So when you run has_nonmovable d = std::move(c), the copy constructor is actually called, because the move constructor of has_nonmovable is deleted (implicitly), it just doesn't exists (even though you explicitly declared the move constructor by expression has_nonmovable(has_nonmovable &&) = default).
But if the move constructor of non_movable was not declared at all, the move constructor would be used for movable (and for every member that has the move constructor) and the copy constructor would be used for nonmovable (and for every member that does not define the move constructor). See the example:
#include <iostream>
struct nonmovable
{
nonmovable() = default;
nonmovable(const nonmovable &) { std::cerr << "nonmovable::copy" << std::endl; }
//nonmovable( nonmovable &&) = delete;
};
struct movable
{
movable() = default;
movable(const movable &) { std::cerr << "movable::copy" << std::endl; }
movable( movable &&) { std::cerr << "movable::move" << std::endl; }
};
struct has_nonmovable
{
movable a;
nonmovable b;
has_nonmovable() = default;
has_nonmovable(const has_nonmovable &) = default;
has_nonmovable( has_nonmovable &&) = default;
};
int main()
{
has_nonmovable c;
has_nonmovable d(std::move(c));
}
It prints:
movable::move
nonmovable::copy
http://coliru.stacked-crooked.com/a/420cc6c80ddac407
Update: But if you comment out the line has_nonmovable(has_nonmovable &&) = default;, then copy will be used for both members: http://coliru.stacked-crooked.com/a/171fd0ce335327cd - prints:
movable::copy
nonmovable::copy
So probably putting =default everywhere still makes sense. It doesn't mean that your move expressions will always move, but it makes chances of this higher.
One more update: But if comment out the line has_nonmovable(const has_nonmovable &) = default; either, then the result will be:
movable::move
nonmovable::copy
So if you want to know what happens in your program, just do everything by yourself :sigh:
Yes, a defaulted move constructor will perform a member-wise move of its base and members, so:
Example(Example&& mE) : a{move(mE.a)}, b{move(mE.b)} { }
is equivalent to:
Example(Example&& mE) = default;
we can see this by going to the draft C++11 standard section 12.8 Copying and moving class objects paragraph 13 which says (emphasis mine going forward):
A copy/move constructor that is defaulted and not defined as deleted
is implicitly defined if it is odrused (3.2) or when it is explicitly
defaulted after its first declaration. [ Note: The copy/move
constructor is implicitly defined even if the implementation elided
its odr-use (3.2, 12.2). —end note ][...]
and paragraph 15 which says:
The implicitly-defined copy/move constructor for a non-union class X
performs a memberwise copy/move of its bases and members. [ Note:
brace-or-equal-initializers of non-static data members are ignored.
See also the example in 12.6.2. —end note ] The order of
initialization is the same as the order of initialization of bases and
members in a user-defined constructor (see 12.6.2). Let x be either
the parameter of the constructor or, for the move constructor, an
xvalue referring to the parameter. Each base or non-static data member
is copied/moved in the manner appropriate to its type:
if the member is an array, each element is direct-initialized with the corresponding subobject of x;
if a member m has rvalue reference type T&&, it is direct-initialized with static_cast(x.m);
otherwise, the base or member is direct-initialized with the corresponding base or member of x.
Virtual base class subobjects shall be initialized only once by the
implicitly-defined copy/move constructor (see 12.6.2).
apart very pathological cases ... YES.
To be more precise, you have also to considered eventual bases Example may have, with exact same rules. First the bases -in declaration order- then the members, always in declaration order.