deprecating copy construction and assignment

In a few instances the implicit definition of a defaulted copy constructor and copy assignment operator is deprecated in C++11. E.g.:

struct A
{
  // A(const A&) = default; // deprecated

  // A& operator=(const A&) = default; // deprecated

  ~A();
};

Should we warn when both -std=c++11 and -Wdeprecated is given?

Howard

In a few instances the implicit definition of a defaulted copy constructor and copy assignment operator is deprecated in C++11. E.g.:

struct A
{
// A(const A&) = default; // deprecated

// A& operator=(const A&) = default; // deprecated

~A();
};

Should we warn when both -std=c++11 and -Wdeprecated is given?

I believe we should, yes - just that no one's gotten around to
implementing this (it's been somewhere on my mental list).

Aside: It'd be rather nice if these rules actually implemented the
classic "rule of three" (if you implement any of the three you should
implement all three - or 5 in C++11) but I don't think the standard
wording goes quite that far.

- David

It comes pretty close to the rule of 3:

The implicitly default copy constructor is deprecated if there is a user-declared copy assignment or destructor.

The implicitly default copy assignment is deprecated if there is a user-declared copy constructor or destructor.

One needs to be more careful with defaulting the move members. It is easy to default them and have them get implicitly deleted:

struct member
{
    member();
    member(const member&);
    member& operator=(const member&);
    ~member();
};

struct A
{
    member m_;

    A() = default;
    A(const A&) = default;
    A& operator=(const A&) = default;
    A(A&&) = default;
    A& operator=(A&&) = default;
    ~A() = default;
};

A is neither move constructible nor move assignable. The following is probably what was intended:

struct A
{
    member m_;

    A() = default;
    A(const A&) = default;
    A& operator=(const A&) = default;
    ~A() = default;
};

which, ironically, *is* move constructible and move assignable. :slight_smile: But I'm not suggesting a warning for this.

Howard

Hello,

One needs to be more careful with defaulting the move members. It is easy to default them and have them get implicitly deleted:

struct member
{
   member();
   member(const member&);
   member& operator=(const member&);
   ~member();
};

struct A
{
   member m_;

   A() = default;
   A(const A&) = default;
   A& operator=(const A&) = default;
   A(A&&) = default;
   A& operator=(A&&) = default;
   ~A() = default;
};

A is neither move constructible nor move assignable. The following is probably what was intended:

I suggest enhancing the error message with an explanation: "A is not move assignable because member 'm_'
has an {explicitly|implicitly} deleted move assignment operator." Or even "... has a move assignment
operator that was implicitly deleted by the explicit copy assignment operator".

Jonathan

I prefer the last one. Users (including me) get confused when we tell
them that something was deleted but not why/how to avoid deleting it.

I just became aware of cwg 1402 which is in ready status:

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1402

This change will no longer make A's move members deleted. Instead A will get move members that copy the data member.

This is a very good change as the current rules are extremely error prone. The library section of the standard itself was bitten by these rules in the move constructor of both tuple and pair (which are explicitly defaulted).

As this issue is both non-controversial, and already in ready status, I think it would be a good idea to go ahead and implement it. That will likely be easier than designing warnings for the implicit deletion of explicitly defaulted move members.

Howard

I agree. I happen to be currently working on the implicit deletion of special member functions anyway, so I’ll make this change.

Could someone explain why the second one is move-constructible and move-assignable while the first one is not?

How does explicitly defaulting the move-constructor and move-assignment cause A to not be move-constructible nor move-assignable, while omitting them makes A be move-constructible and move-assignable?

–Sean Silva

Could someone explain why the second one is move-constructible and move-assignable while the first one is not?

How does explicitly defaulting the move-constructor and move-assignment cause A to *not* be move-constructible nor move-assignable, while omitting them makes A be move-constructible and move-assignable?

Note that application of:

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1402

changes everything. But prior to application of this bug fix to C++11:

struct member
{
   member();
   member(const member&);
   member& operator=(const member&);
   ~member();
};

struct A
{
   member m_;

   A() = default;
   A(const A&) = default;
   A& operator=(const A&) = default;
   A(A&&) = default;
   A& operator=(A&&) = default;
   ~A() = default;
};

A defaulted move constructor (assignment) is implicitly deleted if a base class or non-static data member does not have a move constructor (assignment), or if it has a non-trivial copy constructor (assignment).

After the application of cwg#1402, the above defaulted move constructor (assignment) will not be deleted, but will instead call the copy constructor (assignment) of 'member'. Thus A will be move constructible and move assignable.

A is neither move constructible nor move assignable. The following is probably what was intended:

struct A
{
   member m_;

   A() = default;
   A(const A&) = default;
   A& operator=(const A&) = default;
   ~A() = default;
};

In this example A does not have a move constructor nor move assignment operator. Yet it is move constructible (assignable) because one can construct (assign) it using an rvalue A as the source. The copy constructor (assignment) is used for this operation. cwg#1402 will not impact this example.

Howard

The standard says that a defaulted move-constructor or move-assignment is deleted if a base class or non-static data member has no corresponding move operation, and the corresponding copy operation is non-trivial. This rule was introduced in http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2009/n2904.pdf based on some notion of efficiency concerns.

The result is that explicitly defaulting the move operations gives you defaulted move operations, and not declaring them gives you no move operations. In the latter case, an attempt to move will successfully use the copy operations instead.

The result is that explicitly defaulting the move operations gives you defaulted move operations,

… gives you deleted move operations … :slight_smile:

So even though the latter one is move-constructible, it does it through the copy ctor, so it doesn’t get the performance benefit of the move then, right?

This is now implemented in r153883.

Thanks!

Howard