c++language-lawyerc++20move-constructordefaulted-functions

Can move constructor with (const T&&) parameter be defaulted?


I see a similar question Default move constructor taking a const parameter, which is 8 years old, with the answer No.

But at the same time, a slightly modified program with the constructor defaulted after the class definition:

struct A {
    A(const A&&);
};
A::A(const A&&) = default;

is accepted by EDG 6.7 and recently released GCC 15.1. Online demo: https://gcc.godbolt.org/z/E4qT3sTEq

And even more complex example appears to work correctly with these two compilers:

struct A {
    int i;
    constexpr A(int v) : i(v) {}
    constexpr A(const A&&);
};

constexpr int f() {
    A a(1);
    A b = static_cast<const A&&>( a );
    return b.i;
}

constexpr A::A(const A&&) = default;
static_assert( f() == 1 );

But MSVC still dislikes it:

error C2610: 'A::A(const A &&)': is not a special member function or comparison operator which can be defaulted
<source>(13): note: the argument must be a non-const rvalue reference

as well as Clang:

error: the parameter for an explicitly-defaulted move constructor may not be const

Online demo: https://gcc.godbolt.org/z/6W9W865vG

Has anything changed during the last 8 years in this relation? Which implementation is correct now?


Solution

  • The rule in [dcl.fct.def.default] is:

    An explicitly defaulted special member function F1 is allowed to differ from the corresponding special member function F2 that would have been implicitly declared, as follows:

    • F1 and F2 may have differing ref-qualifiers;
    • if F2 has an implicit object parameter of type “reference to C”, F1 may be an explicit object member function whose explicit object parameter is of (possibly different) type “reference to C”, in which case the type of F1 would differ from the type of F2 in that the type of F1 has an additional parameter;
    • F1 and F2 may have differing exception specifications; and
    • if F2 has a non-object parameter of type const C&, the corresponding non-object parameter of F1 may be of type C&.

    If the type of F1 differs from the type of F2 in a way other than as allowed by the preceding rules, then:

    • if F1 is an assignment operator, [...]
    • otherwise, if F1 is explicitly defaulted on its first declaration, it is defined as deleted;
    • otherwise, the program is ill-formed.

    The implicitly declared move constructor would look like A(A&&). A(A const&&) differs from that in a way that doesn't match any of those four exceptions (there is a special case for defining a copy constructor as C(C&) = default;, that's the fourth bullet, but nothing for move constructors).

    So we fall into the second rule. Had this move constructor been defaulted on its first declaration, it would have been defined as deleted. But in the OP, it's defaulted out of line — so this should be ill-formed.


    In C++17 (as in C++14), this still would have been ill-formed (even if the function had been defaulted on its first declaration). So while the rules around defaulting have been relaxed a bit, those relaxations would not affect this example.