Search code examples
c++c++20c++-concepts

Result of expression convertible to a type that satisfies a concept


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?


Solution

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