理解`std::is_move_constructible`
Understanding `std::is_move_constructible`
没有移动构造函数但具有接受 const T&
个参数的复制构造函数的类型,满足 std::is_move_constructible
。例如,在下面的代码中:
#include <type_traits>
struct T {
T(const T&) {}
//T(T&&) = delete;
};
int main() {
static_assert(std::is_move_constructible<T>::value, "not move constructible");
return 0;
}
T
将没有隐式移动构造函数,因为它有一个用户定义的复制构造函数。
但是,如果我们取消注释移动构造函数的显式删除,代码将不再编译。为什么是这样?我原以为显式复制构造函数仍会满足 std::is_move_constructible
.
重载是否起作用,选择声明的移动构造函数然后因为被删除而失败?
如果标准要求 no implicit move ctor
和 deleted move ctor
class 之间的移动可构造性之间的差异,请引用,并在可能的情况下给出理由(如 "to provide a facility for forbidding move construction"——第一个跳入脑海的事情)。
概览
移动构造函数 class 是 class 具有移动构造函数的隐式或用户声明的。或者为右值引用调用的复制构造函数。这些构造函数将被调用,除非 class 有一个删除的移动构造函数。
因此:
struct T {
T(const T&) {}
};
移动是否可构建class,因此您从各自的特征中得到真实。
通过移动构造函数声明 delete
:
struct T {
T(const T&) {}
T(T&&) = delete;
};
使你的 class 非移动可构造。
为什么会这样?
即使移动构造函数被删除,它也参与了重载决议,就好像它没有被删除一样。因此,首选直接匹配。
为is_move_constructible
提供成员常量value
true 变量定义T obj(T&&);
必须是良构的,如果删除T
的移动构造函数是错误的-形成。因此,您得到的值等于 false。
现在说说这在编译时是如何发生的。寻找 std::declval
的魔力。 std::declval
在某处使用是特征的实现,以评估 class 对象的移动是否可以在不实际评估表达式的情况下进行。
这是对我的第一个答案的彻底修改,纠正了一些错误并引用了标准并确定了提问者希望的一些细节。
std::is_move_constructible
实际做了什么
如果 T
是结构,则 std::is_move_constructible<T>
的计算结果为 std::is_constructible<T,T&&>
。如果 T x(y)
是类型 U
的某些 y
的格式正确的表达式,则 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
然而,标准允许您使用 class 右值初始化 class 左值引用。
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。
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&&)
。但是移动构造函数被删除了,所以这个是无效的。
结论
第一个结构 S
可以使用其可用于执行类似移动的表达式的复制构造函数,因为它是此表达式最专用的构造函数。
第二个结构 T
必须使用其显式声明的移动构造函数来执行类似移动的表达式,同样是因为它是最专业的。然而,该构造函数被删除,移动构造表达式失败。
在我的测试中,答案似乎又错了。顺便说一下,我已经在 MSVC 17 和 GNU 6.3 上测试了以下代码:
struct T {
T(const T&) {}
};
static_assert(is_move_constructible<T>(), "");
T a;
T b(move(a)); // compiler error here
根据此处[http://en.cppreference.com/w/cpp/language/move_constructor],用户定义的复制构造函数将禁用隐式默认移动构造函数。代码无法编译。
但奇怪的是,元函数 is_move_constructible<> 说是,编译器抱怨错误发生在位置: T(b(move(a));
因此,在我看来,is_move_constructible 的实现存在一些缺陷。
我错了,这里忘记了默认的构造函数。
没有移动构造函数但具有接受 const T&
个参数的复制构造函数的类型,满足 std::is_move_constructible
。例如,在下面的代码中:
#include <type_traits>
struct T {
T(const T&) {}
//T(T&&) = delete;
};
int main() {
static_assert(std::is_move_constructible<T>::value, "not move constructible");
return 0;
}
T
将没有隐式移动构造函数,因为它有一个用户定义的复制构造函数。
但是,如果我们取消注释移动构造函数的显式删除,代码将不再编译。为什么是这样?我原以为显式复制构造函数仍会满足 std::is_move_constructible
.
重载是否起作用,选择声明的移动构造函数然后因为被删除而失败?
如果标准要求 no implicit move ctor
和 deleted move ctor
class 之间的移动可构造性之间的差异,请引用,并在可能的情况下给出理由(如 "to provide a facility for forbidding move construction"——第一个跳入脑海的事情)。
概览
移动构造函数 class 是 class 具有移动构造函数的隐式或用户声明的。或者为右值引用调用的复制构造函数。这些构造函数将被调用,除非 class 有一个删除的移动构造函数。
因此:
struct T {
T(const T&) {}
};
移动是否可构建class,因此您从各自的特征中得到真实。
通过移动构造函数声明 delete
:
struct T {
T(const T&) {}
T(T&&) = delete;
};
使你的 class 非移动可构造。
为什么会这样?
即使移动构造函数被删除,它也参与了重载决议,就好像它没有被删除一样。因此,首选直接匹配。
为is_move_constructible
提供成员常量value
true 变量定义T obj(T&&);
必须是良构的,如果删除T
的移动构造函数是错误的-形成。因此,您得到的值等于 false。
现在说说这在编译时是如何发生的。寻找 std::declval
的魔力。 std::declval
在某处使用是特征的实现,以评估 class 对象的移动是否可以在不实际评估表达式的情况下进行。
这是对我的第一个答案的彻底修改,纠正了一些错误并引用了标准并确定了提问者希望的一些细节。
std::is_move_constructible
实际做了什么
如果 T
是结构,则 std::is_move_constructible<T>
的计算结果为 std::is_constructible<T,T&&>
。如果 T x(y)
是类型 U
的某些 y
的格式正确的表达式,则 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
然而,标准允许您使用 class 右值初始化 class 左值引用。
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。
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&&)
。但是移动构造函数被删除了,所以这个是无效的。
结论
第一个结构 S
可以使用其可用于执行类似移动的表达式的复制构造函数,因为它是此表达式最专用的构造函数。
第二个结构 T
必须使用其显式声明的移动构造函数来执行类似移动的表达式,同样是因为它是最专业的。然而,该构造函数被删除,移动构造表达式失败。
在我的测试中,答案似乎又错了。顺便说一下,我已经在 MSVC 17 和 GNU 6.3 上测试了以下代码:
struct T {
T(const T&) {}
};
static_assert(is_move_constructible<T>(), "");
T a;
T b(move(a)); // compiler error here
根据此处[http://en.cppreference.com/w/cpp/language/move_constructor],用户定义的复制构造函数将禁用隐式默认移动构造函数。代码无法编译。 但奇怪的是,元函数 is_move_constructible<> 说是,编译器抱怨错误发生在位置: T(b(move(a));
因此,在我看来,is_move_constructible 的实现存在一些缺陷。
我错了,这里忘记了默认的构造函数。