理解`std :: is_move_constructible`

时间:2015-11-26 13:12:49

标签: c++ c++11 move-constructor

没有移动构造函数但带有接受772, 1173, 1614, 918, 666参数的复制构造函数的类型满足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_constructibleno implicit move ctor类之间的移动构造性之间的差异,请引用,并且如果可能的话,给出一个基本原理(例如&#34;提供设施)禁止移动建设&#34; - 首先想到的事情。)

3 个答案:

答案 0 :(得分:16)

这是对我的第一个答案的彻底修改,纠正了一些错误,并从标准中引用了一些错误,并提出了提问者所希望的一些细节。

std::is_move_constructible实际做什么

如果T是结构,则std::is_move_constructible<T>评估为std::is_constructible<T,T&&>。如果std::is_constructible<T,U>T x(y)类型y的格式正确的表达式,则U有效。因此,要使std::is_move_constructible<T>成立,T x(std::move(y))必须格式正确y类型T

标准引用:

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

但是,该标准允许您使用类rvalue初始化类左值引用。

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)的副本构造函数y和对象T&&,即T的右值引用,那么y就是参考与T&T x(y)兼容的是一个有效的表达式,用于调用复制构造函数T::T(S&)

示例结构的作用

让我继续您的第一个示例,删除const关键字,以避免说明引用需要比初始化程序更多cv限定。

struct S {
    S(S&) {}
};

让我们检查一下情况。由于存在用户定义的复制构造函数,因此没有隐式默认的移动构造函数。然而, 如果y属于S类型,则类型为std::move(y)的{​​{1}}引用与类型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必须使用其显式声明的移动构造函数来执行类似移动的表达式,因为它是最专业的。但是,删除了该构造函数,move-construction表达式失败。

答案 1 :(得分:5)

概述

移动可构造类是一个具有移动构造函数的类,无论是隐式的还是用户声明的。或者是为rvalue引用调用的复制构造函数。除非类具有已删除的移动构造函数,否则将引发这些构造函数。

因此:

struct T {
    T(const T&) {}
};

是一个可移动的构造类,因此你可以从各自的特征中得到真实。

通过移动构造函数delete声明:

struct T {
    T(const T&) {}
    T(T&&) = delete;
};

使你的班级不可移动。

为什么会这样?

即使移动构造函数是delete,它也会参与重载解析,就好像它不是delete一样。因此,首选直接匹配。

要使is_move_constructible提供成员常量value为真,变量定义T obj(T&&);必须格式正确,如果删除T的移动构造函数是格式错误的。因此,您获得的值等于false。

现在关于如何在编译时发生这种情况。寻找std::declval的魔力。在某处使用std::declval是特征的实现,以评估是否可以移动类对象而不实际评估表达式。

答案 2 :(得分:-1)

在我的测试中,答案似乎又错了。顺便说一句,我在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&lt;&gt;是的,并且编译器抱怨错误发生在位置:T(b(move(a));

所以,在我看来,is_move_constructible的实现有一些缺陷。

我的错误,我在这里忘记了默认的构造函数。