27
votes

According to the standard,

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 constructor,

— X does not have a user-declared copy assignment operator,

— X does not have a user-declared move assignment operator, and

— X does not have a user-declared destructor.

Now the following fails to compile

# include <utility>

class Foo
{
public:
  Foo() = default;
  Foo(Foo const &) = delete;
};

int main()
{
  Foo f;
  Foo g(std::move(f)); // compilation fails here
  return 0;
}

So it seems that a deleted function is considered as user-defined, which makes sense (it is not its default implementation). However, in that particular case, how would deleted copy construtor/assignment mess default move constructor/assignment?

I think this question has practical importance because manual generation and esp. maintenance of such default functions is error prone, while at the same time, the (righteous) increase of the use of classes such as std::unique_ptr as class members made non-copyable classes much more common beasts than they used to be.

3
Not sure to understand well your question but why don't you default the move ctor? i.e. Foo(Foo &&) = default;Jean-Baptiste Yunès
It would make much more sense the other way around, because when class is not movable then it almost certainly is not copyable. It's quite common to find non-copyable, but movable classes on the other hand. But this is a C++, a field of a civil law rather than logical system....doc

3 Answers

36
votes

user-declared means either either user-provided (defined by the user), explicitly defaulted (= default) or explicitly deleted (= delete) in contrast with implicitly defaulted / deleted (such as your move constructor).

So in your case, yes the move constructor is implicitly deleted because the copy-constructor is explicitly deleted (and thus user-declared).

However, in that particular case, how would deleted copy constructor/assignment mess default move constructor/assignment?

It would not, but the standard does not make the difference between this case and a complicated one.

The shortest answer is that having an implicitly defined move-constructor with an explicitly deleted copy-constructor might be dangerous in some cases, the same when you have a user-defined destructor and no user-defined copy-constructor (see rule of three/five/zero). Now, you can argue that a user-defined destructor does not delete the copy-constructor, but this is simply a flaw in the language which cannot be removed because it would break a lot of old (bad) program. To quote Bjarne Stroustrup:

In an ideal world, I think we would decide on “no generation” as the default and provide a really simple notation for “give me all the usual operations.” [...] Also, a “no default operations” policy leads to compile time errors (which we should have an easy way to fix), whereas a generate operations by default policy leads to problems that cannot be detected until run time.

You can read more about this in N3174=10-0164.

Note that most people follow the rule of three/five/zero, and in my opinion you should. By implicitly deleting the default move-constructor, the standard is "protecting" you from mistakes and should have protected you a long time before by deleting copy-constructor in some cases (see Bjarne's paper).

Further reading if you are interested:

I think this question has practical importance because manual generation and esp. maintenance of such default functions is error prone, while at the same time, the (righteous) increase of the use of classes such as std::unique_ptr as class members made non-copyable classes much more common beasts than they used to be.

Marking the move constructor as explicitly defaulted will solve this problem:

class Foo {
public:
  Foo() = default;
  Foo(Foo const &) = delete;
  Foo(Foo&&) = default;
};

You get a non-copyable object with a default move constructor, and in my opinion these explicit declarations are better than implicit ones (e.g. by only declaring the move constructor as default without deleting the copy-constructor).

2
votes

As you stated, from §12.8

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 constructor,

  • [...]

Note the user-declared. But if you look at §8.4.3:

A function definition of the form:

attribute-specifier-seqopt decl-specifier-seqopt declarator virt-specifier-seqopt = delete ;

is called a deleted definition. A function with a deleted definition is also called a deleted function.

A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed.

So the standard defines deleted functions as user-declared (as you can see from above), even though they are deleted and can't be used.

Then, according to §12.8, the implicit move constructor is not defined, because of the user-declared (with = delete;) copy constructor.

-1
votes

The behavior can essentially be illustrated with:

void foo(const int& i)
{
    std::cout << "copy";
}

void foo(int&& i)
{
    std::cout << "move";
}

When both overloads are present, the int&& overload is selected for rvalues while the const int& overload is selected for lvalues. If you remove the int&& overload, const int& is called (ergo, not an error) even for rvalues. This is essentially what's happening in this case:

class Foo
{
public:
  Foo() = default;
  Foo(Foo const &) = delete;
};

Overload resolution only sees one candidate but since it is explicitly deleted, the program is ill-formed.

The non-normative note below the section you quoted clarifies this is what's happening:

[ Note: When the move constructor is not implicitly declared or explicitly supplied, expressions that otherwise would have invoked the move constructor may instead invoke a copy constructor. — end note ]