To make a concept checking if a type can be converted without narrowing to another, it is proposed here to make it using std::forward and std::type_identity_t like this:
template<class T, class U>
concept __construct_without_narrowing = requires (U&& x) {
{ std::type_identity_t<T[]>{std::forward<U>(x)} } -> T[1];
};
I understand from it why something like this:
To{std::declval<From>()}
gives incorrect results, but when i try to simplify it using another idea in the paper, writing just
template <typename From, typename To>
concept WithoutNarrowing =
requires (From x) {
{(To[1]){x}}
->std::same_as<To[1]>;
};
It seems to give the same results. What circumstances have to occur for it to give different result? Or is it equivalent? For what reason is std::forward used here?
CodePudding user response:
This is the usual approach for type traits like this that involve some kind of function/constructor argument.
U
is the type from which T
is supposed to be constructed, but if we want to discuss the construction we also need to consider the value category of the argument. It may be an lvalue or a rvalue and this can affect e.g. which constructor is usable.
The idea is that we map the rvalue argument case to a non-reference U
or rvalue reference U
and the lvalue argument case to a lvalue reference U
, matching the mapping of expressions in decltype
and of return types with value categories in function call expressions.
Then, by the reference collapsing rules, U&&
will be a lvalue reference if the constructor argument is a lvalue and otherwise a rvalue reference. Then using std::forward
means that the actual argument we give to the construction will indeed be a lvalue argument when U
was meant to represent one and a rvalue argument otherwise.
Your approach using {(To[1]){x}}
doesn't use the forwarding and so would always only test whether construction from a lvalue can be done without narrowing, which is not what is expected if e.g. U
is a non-reference.
Your approach is further incorrect because (To[1]){x}
is not valid syntax in standard C . If X
is a type you can have X{x}
or (X)x
, but not (X){x}
. The last syntax is part of C however and called a compound literal there. For that reason a C compiler may support it as an extension to C . That's why the original implementation uses the round-about way with std::type_identity_t
.
The implementation seems to also be written for an earlier draft of C 20 concepts. It is now not possible to use types to the right of ->
directly for a requirement. Instead a concept, i.e. -> std::same_as<T[1]>
, must be used as in your suggested implementation.
CodePudding user response:
well there is difference between (U u)
, (U& u)
and (U&& u)
that std::forward
is supposed to preserve. in case of (U u)
the type has to have defined a copy constructor (since (U u)
basically means "pass a copy of")