12

Since concepts are defined as compile-time predicates, is it also possible to actually reuse these predicates for compile-time algorithms? For example would it be possible to check whether all types in a tuple conform to a concept? As far as I have seen it is not possible to pass a concept to a function in any way, which kind of leads me back to using templates for these cases.

#include <type_traits>

template<typename T>
concept FloatLike = std::is_same_v<T, float>;

struct IsFloat
{
    template<typename U>
    constexpr static bool test()
    {
       return FloatLike<U>;
    }
};


template<typename Predicate, typename... T>
constexpr bool all_types()
{
    return (Predicate::template test<T>() && ...);
}


int main()
{
   static_assert(all_types<IsFloat, float, float>());
   static_assert(!all_types<IsFloat, float, int>());
}

What I would like to do is something like this, so I don't have to wrap the concept all the time to be able to use it:

template<concept Predicate, typename... T>
constexpr bool all_types()
{
    return (Predicate<T> && ...);
}


int main()
{
   static_assert(all_types<FloatLike, float, float>());
   static_assert(!all_types<FloatLike, float, int>());
}

Is there any way to get closer to this?

Andreas Loanjoe
  • 2,205
  • 10
  • 26
  • And then there will be a proposal to add concepts of concepts... BTW, `all_types()` can be significantly simplified using fold expressions `... &&`: `return (... && Predicate::template test());` – Evg Nov 15 '19 at 11:32
  • @Evg it would be great :) – Igor R. Nov 15 '19 at 11:35

2 Answers2

5

Is there any way to get closer to this?

Well, no, not really. Not in C++20. There is no notion in the language today of a template concept-parameter. Even variable templates cannot be used as template parameters. So if have a concept to begin with, we can't avoid wrapping.

But what we can do is write simpler wrappers. If we agree to use "old style" type traits as predicates, specifically those that behave like std::integral_constants, then we can have ourselves pretty terse "concept" definitions that can be used as predicates.

template<typename T>
using FloatLike = std::is_same<T, float>;

template<template <typename> class Predicate, typename... T>
constexpr bool all_types()
{
    return (Predicate<T>{} && ...);
}

It's as good as it can get, as far as I can see.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • Would this work by decltyping a generic lambda as a template template in any way? It seems like a lambda is never a template though right, only the call operator? – Andreas Loanjoe Nov 15 '19 at 13:15
  • @AndreasLoanjoe - Indeed. A lambda is never a template. But if you are willing to pass lambdas around, then C++20 allows you to do it. I can add a variant of that in a few minutes. – StoryTeller - Unslander Monica Nov 15 '19 at 13:20
  • @AndreasLoanjoe - On second thought, a lambda still comes out very verbose. I don't think it's a great alternative. Here it is anyway https://godbolt.org/z/QSHy8X – StoryTeller - Unslander Monica Nov 15 '19 at 13:24
  • I hope they will add something better :), but yeah it seems like this is the answer, only style type traits do offer this functionality concepts don't (yet). – Andreas Loanjoe Nov 15 '19 at 13:35
0

If your goal is to "check whether all types in a tuple conform to a concept", then you can do something like this:

// concept to check if all types in Ts are the same as T
template<typename T, typename... Ts>
concept AllSame = (std::is_same_v<T,Ts> && ...);

// function only accepts floats as template parameters
template<AllSame<float>... Floats>
constexpr void float_foo()
{
}

// function only accepts ints as template parameters
template<AllSame<int>... Ints>
constexpr void int_foo()
{
}

// function only accepts T as template parameters
template<typename T, AllSame<T>... Ts>
constexpr void foo()
{
}

int main()
{
    int_foo<int, int, int>();
    // int_foo<int, int, double>(); // fails to compile
    float_foo<float, float, float>();
    // float_foo<float, float, int>(); // fails to compile
    foo<int, int, int, int>();
    // foo<int, int, int, float>(); // fails to compile
    foo<double, double, double, double>();
    // foo<double, double, double, int>(); // fails to compile

}

LIVE DEMO

serkan.tuerker
  • 1,681
  • 10
  • 20
  • Why is your `AllSame` variadic? Each template parameter in a pack introduced by a *type-constraint* is separately constrained already. – Davis Herring Nov 26 '19 at 03:57
  • @DavisHerring I don't understand. Do you mean the concept itself or the template parameters in `*_foo()`? – serkan.tuerker Nov 26 '19 at 04:06
  • I mean that the code you have works if you remove the `...` on `Ts` and the `&& ...` that uses it. (Obviously the name `AllSame` would then be inappropriate, but I’m not sure why I’d want to express a count in unary as `` anyway.) – Davis Herring Nov 26 '19 at 04:16
  • @DavisHerring Then the concept wouldn't be `AllSame` but `SameAs` (see https://en.cppreference.com/w/cpp/concepts/same_as) and OP wanted to have a concept which takes a variadic number of template parameters. – serkan.tuerker Nov 26 '19 at 04:20
  • Obviously it would be `std::same_as`. I don’t think the variadic part was the point: it was the (desired) variable *identity* of the concept. And my point was that the variadic aspect of your concept example was irrelevant to its use (because non-variadic concepts already work with template parameter packs). – Davis Herring Nov 26 '19 at 04:32
  • @DavisHerring Now I understand what you mean, and yes, you are right. Didn't know that, thanks for clarifying :) – serkan.tuerker Nov 26 '19 at 04:40