In a requirement expression (pardon if my terminology is incorrect) in each requirement there is an expression and optionally a type-constraint. The latter has to be a concept. For example:
template<typename T>
concept HasBar = requires( T t ) { { t.bar } -> std::convertible_to<float>; };
Similarly to std::convertible_to
, one can make up a concept called decays_to
or something.
What is then the most simple and clean way to require that an expression results in something that is convertible-to / decays-to a concept (well, some type that satisfies a concept) like std::floating_point
, std::integral
, MyConcept
, etc? Is one supposed to make semi-duplicates of these concepts? Like:
ConvertibleToFloatingPoint
, DecaysToFloatingPoint
, DecaysToMyConcept
, etc?
"Convertible to some T matching concept C" is fundamentally impossible, the compiler isn't going to check all possible conversion targets.
"Decays to a type matching concept C" is possible, but not with the prettiest syntax.
It can't be DecaysTo<std::convertible_to<float>>
because concepts can't be passed as template parameters. Traits can be passed though:
template <typename T, template <typename...> typename Trait, typename ...P>
concept DecaysTo = Trait<std::decay_t<T>, P...>::value;
And then: { t.bar } -> DecaysTo<std::is_convertible, float>;
Or, a less convoluted approach would be:
template<typename T>
concept HasBar = requires(T t)
{
requires std::convertible_to<std::decay_t<decltype(t.bar)>, float>;
};
Or you can cheat by forcing the decay in the expression, e.g.:
template<typename T>
concept HasBar = requires(T t)
{
{ auto(t.bar) } -> std::convertible_to<float>;
};
But this requires bar
to be copyable, so IMO this is worse.