这是对我的第一个答案的全面修改,以纠正所说的一些错误并引用标准并指出提问者希望的一些细节。
std::is_move_constructible 实际做了什么
如果T 是一个结构,那么std::is_move_constructible<T> 的计算结果为std::is_constructible<T,T&&>。如果T x(y) 是一些y 类型为U 的格式良好的表达式,则std::is_constructible<T,U> 有效。因此,要使std::is_move_constructible<T> 为真,T x(std::move(y)) 对于T 类型的y 必须是格式良好的。
引用标准:
The predicate condition for a template specialization is_constructible<T, Args...>
shall be satisfied if and only if the following variable definition would
be well-formed for some invented variable t:
T t(create<Args>()...);
(...)
Template: template <class T> struct is_move_constructible;
Condition: For a referenceable type T, the same result as is_constructible<T, T&&>::value,
otherwise false.
Precondition: T shall be a complete type, (possibly cv-qualified) void,
or an array of unknown bound.
创建移动构造函数时
标准规定,仅当用户未声明复制构造函数、移动构造函数、赋值运算符或析构函数时,才会创建默认移动构造函数。
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
—X does not have a user-declared copy constructor,
—X does not have a user-declared copy assignment operator,
—X does not have a user-declared move assignment operator, and
—X does not have a user-declared destructor
但是,该标准允许您使用类右值初始化类左值引用。
Otherwise, the reference shall be an lvalue reference to a non-volatile const type
(i.e., cv1 shall be const), or the reference shall be an rvalue reference.
—If the initializer expression is an xvalue (but not a bit-field),
class prvalue, array prvalue or function lvalue and “cv1 T1”
is reference-compatible with “cv2 T2”, or (...)
then the reference is bound to the value of the initializer expression (...)
(or, in either case, to an appropriate base class subobject).
因此,如果您有一个复制构造函数T::T(S& other) 和一个T&& 类型的对象y,即对T 的右值引用,那么y 与T& 和T x(y) 的引用兼容是调用复制构造函数T::T(S&) 的有效表达式。
示例结构的作用
让我以您的第一个示例为例,删除 const 关键字,以避免声明引用需要比初始化程序更具 cv-qualified 十次。
struct S {
S(S&) {}
};
让我们检查一下情况。由于存在用户定义的复制构造函数,因此没有隐式默认的移动构造函数。然而,
如果y 属于S 类型,则std::move(y) 属于S&& 类型,与S& 类型的引用兼容。因此S x(std::move(y)) 完全有效并调用复制构造函数S::S(const S&)。
第二个例子做了什么
struct T {
T(T&) {}
T(T&&) = delete;
};
同样,没有定义移动构造函数,因为有一个用户定义的复制构造函数和一个用户定义的移动构造函数。再次让y 为T 类型并考虑T x(std::move(y))。
但是,这一次表达式中可以容纳多个构造函数,因此执行了重载选择。仅尝试使用最专业的匹配构造函数,因此仅尝试调用移动构造函数T::T(T&&)。但是move构造函数被删除了,所以这是无效的。
结论
第一个结构 S 可以使用其复制构造函数来执行类似移动的表达式,因为它是该表达式最专用的构造函数。
第二个结构T 必须使用其显式声明的移动构造函数来执行类似移动的表达式,因为它是最专业的。然而,该构造函数被删除,移动构造表达式失败。