Why cannot concepts be passed to template meta-functions?

Viewed 77

Consider any of the common type-level algorithms provided by libraries such as Boost.MP11, Brigand, etc... For instance:

template<typename... Args>
struct TypeList;

using my_types = TypeList<int, float, char, float, double>;

constexpr int count = boost::mp11::mp_count_if<my_types, std::is_floating_point>::value;
// this holds:
static_assert(count == 3);

Notice that std::is_floating_point could be defined as:

template<typename T>
struct is_floating_point { constexpr bool value = __compiler_magic(T); };

And likewise, we have the std::floating_point concept

template<typename T>
concept floating_point = requires (T t) { __other_compiler_magic(T); };

Sadly, despite the similarity, there does not seem to be an easy way to write something like this without introducing a manually-named wrapper for the concept:

constexpr int count = boost::mp11::count_if<my_types, std::floating_point>::value;

My question is: why cannot concepts be passed in place of types at this point ? Is it a lack of standardization, or is it something that these libraries can solve by providing more overloads ?

It looks like every concept has to be wrapped in a templated type which will just call the concept on its template argument.

From the outside, concepts just look like meta-functions whose domain is {set of types} -> bool. Compilers are able to delay passing parameters to "traditional" type-based metafunctions such as std::is_floating_point, why can't the same seem to happen with concepts ?

1 Answers

The literal answer is that we have template template parameters but not concept template parameters, so you can't pass a concept as a template argument.

The other literal answer is that it was never part of the original concepts proposal and nobody has put in the effort to suggest it as an extension (although I've been collecting use-cases).

One thing that would have to be answered is how dependent concepts affect subsumption - since currently use of concepts is never dependent and so figuring out subsumption is straightforward (actually, it's still not straightforward at all, but at least all the things you need are right there). But in a scenario like:

template <template <typename> concept C, typename T> 
    requires C<T>
void foo(T); // #1

template <typename T>
void foo(T); // #2

Probably if #1 is viable, you want to say it's a beter candidate than #2 since it's still constrained while the other is not. Maybe that's trivial. But then:

template <template <typename> concept C, typename T> 
    requires C<T>
void bar(T); // #3

template <OtherConcept T>
void bar(T); // #4

Let's say #3 and #4 are both viable, is it possible to say which is better? We generally say a whole overload is always better than a different one - but that might not be the case here. Maybe this is just ambiguous?

That seems to me like the main question that would need to be answered in order to get concept template parameters.

The other question might be, can I write foo<convertible_to<int>>(42). convertible_to<int> isn't really a unary concept, but it is a type-constraint that is treated as one in certain contexts, so I would still expect that to work.


Once we have such a thing, I'm sure Boost.Mp11 will quickly acquire something like:

template <template <typename...> concept C>
struct mp_quote_c {
    template <typename... T>
    using fn = mp_bool<C<T...>>;
};

So that you can write:

constexpr int count = mp_count_if_q<my_types, mp_quote_c<std::floating_point>>::value;
Related