Search code examples
c++c++20move-semanticsc++-conceptsmove-constructor

understanding c++ move_constructible concept implementation


I've got the following implementation of the c++ concept move_constructible from cppreference

template<typename _Tp>
concept move_constructible =
    constructible_from<_Tp, _Tp> &&
    convertible_to<_Tp, _Tp>;

I don't get why this works. I presume any type can be converted to itself, so the second requirement is pointless (God, I must be very wrong about something). Also, for the first requirement I would have expected something like constructible_from<_Tp, _Tp&&> to check if the type can be constructed from rvalue-ref (thus, moved).

Please explain how this implementation works.


Solution

  • Most traits/concepts automatically add && to the types of "source" arguments (things that are passed to functions, as in std::is_invocable, or constructed from, as in std::is_constructible).

    I.e. constructible_from<A, B> is equivalent to constructible_from<A, B &&> (&& is automatically added to the second argument, but not to the first), and convertible_to<A, B> is equivalent to convertible_to<A &&, B>.

    Note that if a type already includes &, adding && to it has no effect. So, while T and T && are equivalent here, T & is not.


    This can be inferred from those traits/concepts being defined in terms of std::declval<T>(), which returns T &&.

    For the reason why std::declval<T &>() returns T &, see reference collapsing.


    NOTE: There is one exception: passing an incomplete type (such as a class that was declared but not defined) as a non-first argument to is_constructible (and constructible_from) is UB, and some compilers reject it. Appending && manually works around this rule, so in generic code, you should always prefer passing T && to those traits.

    But specifically for move_constructible there's no difference, since it makes no sense for incomplete types in the first place.