Search code examples
c++c++11language-lawyermove-semanticscompiler-bug

Impossible implicit move operations?


As far as I understand [class.copy.ctor] and [class.copy.assign], struct A in the following code should not be move-constructible nor move-assignable:

#include <type_traits>


struct X {
  X() noexcept; // user-declared default constructor
  ~X() noexcept; // Force X not to be trivially copyable
  X(X &&) = delete; // Explicitly deleted move constructor
  X(X const &) = delete; // Explicitly deleted copy constructor
  X & operator=(X &&) = delete; // Explicitly deleted move assignment operator
  X & operator=(X const &) = delete; // Explicitly deleted copy assignment op.
};
static_assert(!std::is_copy_constructible<X>::value, "");
static_assert(!std::is_copy_assignable<X>::value, "");
static_assert(!std::is_move_assignable<X>::value, "");
static_assert(!std::is_move_constructible<X>::value, "");
static_assert(!std::is_trivially_copyable<X>::value, "");
static_assert(!std::is_trivially_copy_assignable<X>::value, "");
static_assert(!std::is_trivially_copy_constructible<X>::value, "");
static_assert(!std::is_trivially_move_assignable<X>::value, "");
static_assert(!std::is_trivially_move_constructible<X>::value, "");


struct A {
  A() noexcept; // user-declared default constructor
  A(A const &) noexcept; // user-declared copy constructor
  A & operator=(A const &) noexcept; // user-declared copy assignment operator
  X x;
};
static_assert(std::is_copy_constructible<A>::value, "");
static_assert(std::is_copy_assignable<A>::value, "");
static_assert(!std::is_move_assignable<A>::value, "");        // FAILS?!
static_assert(!std::is_move_constructible<A>::value, "");     // FAILS?!
static_assert(!std::is_trivially_copyable<A>::value, "");
static_assert(!std::is_trivially_copy_assignable<A>::value, "");
static_assert(!std::is_trivially_copy_constructible<A>::value, "");
static_assert(!std::is_trivially_move_assignable<A>::value, "");
static_assert(!std::is_trivially_move_constructible<A>::value, "");

However, two of the static assertions fail with both GCC and Clang, which mean that for some reason A is move-assignable and move-constructible.

In my reasoning this shouldn't be, because struct A:

  • does not explicitly declare a move constructor
  • does not explicitly declare a move assignment operator
  • has a user-declared copy constructor
  • has a user-declared copy assignment operator.
  • has a field x of type X which is can not be direct-initialized with any other A::x, because all constructors of X which would participate in overload resolution for this are explicitly deleted.

Is this a compiler bug or am I misunderstanding something?


Solution

  • They're expected behaviors, because the existence of copy constructor and copy assignment operator satisfies the requirement of MoveConstructible

    A class does not have to implement a move constructor to satisfy this type requirement: a copy constructor that takes a const T& argument can bind rvalue expressions.

    and MoveAssignable.

    The type does not have to implement move assignment operator in order to satisfy this type requirement: a copy assignment operator that takes its parameter by value or as a const Type&, will bind to rvalue argument.

    Note that std::is_move_constructible and std::is_move_assignable are just checking an object of the specified type can be constructed/assigned from an rvalue argument. Even there're no move constructor/assignment operator, copy constructor/assignment operator could do the work, because an rvalue argument could be passed to an lvalue reference to const too.

    EDIT

    Note for the sample code you showed the move constructor/assignment operator are not implcitly declared at all (because of the existence of user-declared copy constructor and copy assignment operator), so they won't affect overload resolution and the result that copy constructor/assignment operator would be invoked. But if you explicitly declared them as delete, behavior will change because explicitly deleted functions participate into overload resolution, and they will be selected preferentially, and then std::is_move_constructible and std::is_move_assignable will return false.