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.
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.