不可能的隐式移动操作?

时间:2017-01-30 15:26:49

标签: c++ c++11 language-lawyer move-semantics compiler-bug

据我了解[class.copy.ctor][class.copy.assign],以下代码中的struct A不应该是可移动构造的,也不应该是可移动的:

#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, "");

然而,两个静态断言都失败了GCC和Clang,这意味着由于某种原因,A是可移动分配和可移动构造的。

在我的推理中,这不应该是,因为struct A

  • 未明确声明移动构造函数
  • 未明确声明移动赋值运算符
  • 具有用户声明的复制构造函数
  • 有一个用户声明的复制赋值运算符。
  • x类型的字段X,无法使用任何其他A::x进行直接初始化,因为X的所有构造函数都会参与重载解析为此明确删除。

这是编译错误还是我误解了什么?

2 个答案:

答案 0 :(得分:6)

他们预期的行为,因为复制构造函数和复制赋值运算符的存在满足MoveConstructible的要求

  

一个类不必实现一个移动构造函数来满足这个类型的要求:一个带有const T&amp;的复制构造函数。参数可以绑定rvalue表达式。

MoveAssignable

  

该类型不必实现移动赋值运算符以满足此类型要求:通过值或作为const类型&amp;获取其参数的复制赋值运算符将绑定到rvalue参数。

注意std::is_move_constructiblestd::is_move_assignable只是检查可以从rvalue参数构造/赋值的指定类型的对象。即使没有移动构造函数/赋值运算符,复制构造函数/赋值运算符也可以完成工作,因为rvalue参数也可以传递给const的左值引用。

修改

请注意,您显示的示例代码根本没有声明移动构造函数/赋值运算符(因为存在用户声明的复制构造函数和复制赋值运算符),因此它们不会影响重载解析和将调用复制构造函数/赋值运算符的结果。但是如果你明确地将它们声明为delete,行为将会改变,因为显式删除的函数会参与重载解析,并且会优先选择它们,然后std::is_move_constructiblestd::is_move_assignable将返回{{1} }}

答案 1 :(得分:2)

Require all denied 不会检查是否存在移动构造函数或移动赋值运算符。它检查是否可以从r值引用构造/分配类。在这种情况下,缺少移动设施的类只会使用复制构造函数/赋值运算符。

一般来说,exampleSubSite.com/exampleSubSiteFolder/弱于#include <iostream> using namespace std; template <class t> void sign(t a) { try { throw a; } catch (int i) { cout << "int"; throw; } } main() { try { sign<int>(1); sign<int>('1'); } catch (...) { cout << "caught"; } } :以后,只有移动类型才会失败,前者通过检查。