Passing a concept to a function
Asked Answered
C

2

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?

Comparison answered 15/11, 2019 at 11:11 Comment(2)
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<Ts>());Utilitarian
@Utilitarian it would be great :)Caller
N
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.

Nuncia answered 15/11, 2019 at 12:2 Comment(4)
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?Comparison
@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.Nuncia
@AndreasLoanjoe - On second thought, a lambda still comes out very verbose. I don't think it's a great alternative. Here it is anyway godbolt.org/z/QSHy8XNuncia
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).Comparison
B
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

Bulldoze answered 25/11, 2019 at 19:49 Comment(6)
Why is your AllSame variadic? Each template parameter in a pack introduced by a type-constraint is separately constrained already.Steamer
@DavisHerring I don't understand. Do you mean the concept itself or the template parameters in *_foo()?Bulldoze
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 <int,int,int> anyway.)Steamer
@DavisHerring Then the concept wouldn't be AllSame but SameAs (see en.cppreference.com/w/cpp/concepts/same_as) and OP wanted to have a concept which takes a variadic number of template parameters.Bulldoze
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).Steamer
@DavisHerring Now I understand what you mean, and yes, you are right. Didn't know that, thanks for clarifying :)Bulldoze

© 2022 - 2024 — McMap. All rights reserved.