6

I have inherited the following:

template <typename T>
concept IsAwaiter = requires {
  typename T::await_ready;
  typename T::await_suspend;
  typename T::await_resume;
};

template <typename ...AWAITABLES>
concept IsAwaitables = typename std::conjunction<IsAwaiter<AWAITABLES>...>::type;

Building this with clang 10.0.0 results in the following error:

IsAwaiter.h:43:50: error: template argument for template type parameter must be a type

Perhaps just a simple syntax issue, but I've found it hard to find an example which shows how to create a concept based on a variadic template concept parameter.

Any help appreciated!

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982

2 Answers2

9

std::conjunction is for type traits. std::conjunction<IsAwaiter<AWAITABLES>...> is a type with a member static bool value = (IsAwaiter<AWAITABLES>::value && ...), where each IsAwaiter<AWAITABLES> is itself expected to be a type trait with its own static bool member value. This is nonsensical when IsAwaiter<AWAITABLES> is a concept, because concepts are not type traits. They are "just" booleans. Use a fold expression.

template <typename... AWAITABLES>
concept IsAwaitables = (IsAwaiter<AWAITABLES> && ...);

That's it.

struct Dummy {
    using await_ready = Dummy;
    using await_suspend = Dummy;
    using await_resume = Dummy;
};

int main() {
    static_assert(IsAwaitables<>);
    static_assert(IsAwaitables<Dummy>);
    static_assert(IsAwaitables<Dummy, Dummy>);
}
HTNW
  • 27,182
  • 1
  • 32
  • 60
  • thanks for your clear answer, HTNW. It seems like it is a fairly common use case so hopefully others find it useful, too. – Bob Pretzker Sep 08 '20 at 18:57
7

As HTNW points out, you want:

template <typename ...T>
concept IsAwaitables =  (IsAwaiter<T> && ...);

But really, do you even need this concept at all? You can just use IsAwaiter directly. And it probably should just be named Awaiter - the typical convention for concepts is to name them as nouns rather than questions (e.g. Range vs IsRange).

If you're taking a parameter pack, you would want to use it anyway:

template <Awaiter... T>
void f(T... awaiters);

and the same with the abbreviated function template syntax:

void f(Awaiter auto... awaiters);

or if you have a fixed number of them, it especially doesn't make sense:

template <Awaiter T, Awaiter U>
void f(T, U);

And even in other contexts where it doesn't neatly fit, it seems better to just manually use Awaiter with a fold-expression. So I question the need for the conjunction concept to begin with.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • I still think there is some merrit to variadic concepts, but please change my perspective if there is a better alternative or if this goes against the *typical convention*. For example, when you have a templated `struct` as in `template struct t { ... };` and then some templated member functions that are dependent on the constraint of *myconcept*, as in `template requires (std::convertible_to or ...) auto f(T arg) { ... }`. Having to write the same `requires` clause over and over again instead of using a variadic `concept` feels rather bloaty. – 303 Jun 20 '22 at 01:17