Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
101 views
in Technique[技术] by (71.8m points)

c++ - Understanding `std::is_move_constructible`

Types without a move constructor, but with a copy constructor that accepts const T& arguments, satisfy std::is_move_constructible. For example, in the following code:

#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 will have no implicit move constructor as it has a user-defined copy constructor.

However, if we uncomment the explicit delete of the move constructor, the code no longer compiles. Why is this? I would have expected that the explicit copy constructor would still satisfy std::is_move_constructible.

Does overload play a role, choosing the declared move constructor and then failing because it is deleted?


If this difference between move constructibility between a no implicit move ctor and a deleted move ctor class is mandated by the standard, please quote, and if possible, give a rationale (like "to provide a facility for forbidding move construction"—first thing that jumps to mind).

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This is a complete revamp of my first answer, to correct some mistakes said and to have quotes from the standard and to nail some details the questioner wishes.

What std::is_move_constructible actually do

If T is a structure then std::is_move_constructible<T> evaluates to std::is_constructible<T,T&&>. std::is_constructible<T,U> is valid if T x(y) is a well-formed expression for some y of type U. Thus for std::is_move_constructible<T> to be true, T x(std::move(y)) must be well-formed for y of type T.

Quotes from the standard:

The predicate condition for a template specialization is_constructible<T, Args...>
shall be satis?ed if and only if the following variable de?nition 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-quali?ed) void,
              or an array of unknown bound.

When a move constructor is created

The standard says a default move constructor is created only when no copy constructor, move constructor, assignment operator or a destructor has been declared by the user.

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

However the standard allows you to initialize a class lvalue-reference with a class 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-?eld),
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).

Thus if you have a copy constructor T::T(S& other) and an object y of type T&&, i.e. a rvalue reference to T, then y is reference-compatible with T& and T x(y) is a valid expression to call the copy constructor T::T(S&).

What the example struct do

Let me go with your first example, removing the const keywords, to avoid stating ten times that the reference needs to be more cv-qualified than the initializer.

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

Let's check the condition. There is no implicitly defaulted move constructor since there is a user-defined copy constructor. However, if y is of type S, then std::move(y), of type S&&, is reference compatible with type S&. Thus S x(std::move(y)) is perfectly valid and call the copy constructor S::S(const S&).

What the second example do

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

Again, no move constructor is defined as there is a user defined copy constructor and a user-defined move constructor. Again let y be of type T and consider T x(std::move(y)).

However, this time multiple constructor can fit in the expression, so an overload selection is performed. Only the most specialized matching constructor is attempted to work with so only the move constructor T::T(T&&) is attempted to call to. But the move constructor is deleted, so this is invalid.

Conclusion

The first struct, S, can use its copy constructor usable to perform a move-like expression, as it is the most specialized constructor for this expression.

The second struct, T, have to use its explicitly declared move constructor to perform the move-like expression, again because it is the most specialized. However that constructor is deleted, the move-construction expression fails.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...