Jason Merrill
2012-04-16
Revision 4
N3667

Drafting for Core 1402

Introduction

This paper represents a small change to the drafting for core issue 1402 from the pre-meeting mailing, placed into a separate document so that it can be moved at Bristol.

Issue text

1402. Move functions too often deleted

Section: 12.8  [class.copy]     Status: open     Submitter: Daniel Krügler     Date: 2011-10-03

(From messages 20571 through 20590.)

Paragraphs 11 and 23 of 12.8 [class.copy] make a defaulted move constructor and assignment operator, respectively, deleted if there is a subobject with no corresponding move function and for which the copy operation is non-trivial. This seems excessive and unnecessary. For example:

    template<typename T>
     struct wrap
     {
      wrap() = default;

    #ifdef USE_DEFAULTED_MOVE
      wrap(wrap&&) = default;
    #else
      wrap(wrap&& w) : t(static_cast<T&&>(w.t)) { }
    #endif

      wrap(const wrap&) = default;

      T t;
     };

    struct S {
      S(){}
      S(const S&){}
      S(S&&){}
    };

    typedef wrap<const S> W;

    W get() { return W(); }  // Error, if USE_DEFAULTED_MOVE is defined, else OK

In this example the defaulted move constructor of wrap is selected by overload resolution, but this move-constructor is deleted, because S has no trivial copy-constructor.

I think that we overshoot here with the delete rules: I see no problem for the defaulted move-constructor in this example. Our triviality-deduction rules already cover this case (12.8 [class.copy] paragraph 12: W::W(W&&) is not trivial) and our exception-specification rules (15.4 [except.spec] paragraph 14) already correctly deduce a noexcept(false) specification for W::W(W&&).

It would still be OK to prevent that a move-constructor would be generated for the following example where no user-declared defaulted copy/move members are present:

    template<typename T>
     struct wrap_2
     {
      wrap_2() = default;
      T t;
     };

    typedef wrap_2<const S> W2;

    W2 get() { return W2(); }  // OK, selects copy constructor

if we want. This would mean that we add a new bullet to 12.8 [class.copy] paragraph 9 and paragraph 20.

Drafting note

After extensive discussion on the reflector, in EWG and over lunch with Daveed, Howard and Lawrence, I think we've settled on two points:

Proposed Resolution

Change 12.8¶9-11:
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 [ 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 ]

The implicitly-declared move constructor for class X will have the form

X::X(X&&)
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 defaulted move constructor that is defined as deleted is ignored by overload resolution (13.3, 13.4). [ Note: A deleted move constructor would otherwise interfere with initialization from an rvalue which can use the copy constructor instead. --end note ]
Change 12.8¶20-23:
If the definition of a class X does not explicitly declare a move assignment operator, one will be implicitly declared as defaulted if and only if [ Example: The class definition
struct S {
  int a;
  S& operator=(const S&) = default;
};
will not have a default move assignment operator implicitly declared because the copy assignment operator has been user-declared. The move assignment operator may be explicitly defaulted.
struct S {
  int a;
  S& operator=(const S&) = default;
  S& operator=(S&&) = default;
};
— end example ]

The implicitly-declared move assignment operator for a class X will have the form

X& X::operator=(X&&);

The implicitly-declared copy/move assignment operator for class X has the return type X&; it returns the object for which the assignment operator is invoked, that is, the object assigned to. An implicitly-declared copy/move assignment operator is an inline public member of its class.

A defaulted copy/move assignment operator for class X is defined as deleted if X has:

A defaulted move assignment operator that is defined as deleted is ignored by overload resolution (13.3, 13.4).
Add to the end of 13.3.1:
A defaulted move constructor or assignment operator (12.8) that is defined as deleted is excluded from the set of candidate functions in all contexts.