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

Wildcard for C++ concepts saying "accepting anything for this template argument"


Is there a way to allow a concept with template arguments, to be ok with any template parameter provided?

I.e. some kind of wildcard magic for template argument placeholder?

A usage example:

template<class Me, class TestAgainst>
concept derived_from_or_same_as = 
    std::same_as<Me, TestAgainst> ||
    std::derived_from<Me, TestAgainst>;

Above is needed because unfortunately primitive types behave differently than class types for is_base_of and derived_from.

Now we can define a Pair concept that checks the provided types:

template<class P, class First, class Second>
concept Pair = requires(P p) {
    requires derived_from_or_same_as<decltype(p.first), First>;
    requires derived_from_or_same_as<decltype(p.second), Second>;
};

Use case [a] - accept any valid pair of As or sub-type of As:

// this works well
void doWithPairOfA(const Pair<A, A> auto& p) { /* */ }

Use case [b] - accept any valid pair, no restrictions on the inner types:

// this is *pseudo code* as Pair<auto, auto> is not allowed
void doWithAnyPair(const Pair<auto, auto> auto& p) { /* */ }

Unfortunately, auto is not allowed as template argument placeholder in C++20.

So Pair<auto, auto> is not the solution for now.


Other languages allow such a syntax in a way, though not with the same exact semantics and meaning as requested here, but the usage looks quite similar.

Python:

// Any as wildcard
foo(lst: List[Any]) -> List[str]

Java:

// '?' as wildcard
List<String> foo(List<?> lst)

The pre C++20 syntax would look something like1:

Use case [a] - trying to accept any valid pair of As or sub-type of As:

// not as good as with concepts above, this allows only "pair" of A and A
// **but rejects sub-types of A, which is not good**
// and there is no check that this is actually a pair (can be added with SFINAE)
template<template<class, class> typename PAIR>
void doWithPairOfA(const PAIR<A, A>& p) { /* */ }

Use case [b] - accept any valid pair, no restrictions on the inner types:

// not as good as we would wish - we do allow any kind of "pair"
// but there is no check that this is actually a pair (can be added with SFINAE)
template<template<class, class> typename PAIR, typename ANY1, typename ANY2>
void doWithAnyPair(const PAIR<ANY1, ANY2>& p) { /* */ }

Pre C++20 code

Can concepts present a better solution?


1 Related question (pre C++20) on templates: Templates accepting “anything” in C++


Solution

  • You can achieve wildcard behavior by modifying the Pair concept to accept and check a tag type Any.

    Let's first declare Any as a tag class, no need to implement it.

    class Any;
    

    Now we can create a type_matches concept to check if a type T matches a given type A, with the following rules:

    T matches A

    • if A is Any -- or --
    • if T==A or if T is derived from A

    As noted in the question, the check for T==A or T is derived from A can be made for class types just with std::derived_from however primitive types require adding the test for std::same_as.

    The wildcard match would be achieved with the following code:

    template<class Me, class TestAgainst>
    concept type_matches =
        std::same_as<TestAgainst, Any> ||
        std::same_as<Me, TestAgainst>  ||
        std::derived_from<Me, TestAgainst>;
    

    The Pair concept would be modified to:

    template<class P, class First, class Second>
    concept Pair = requires(P p) {
        requires type_matches<decltype(p.first), First>;
        requires type_matches<decltype(p.second), Second>;
    };
    

    The code can allow now both required use cases.

    Use case [a] - accept any valid pair of As or sub-type of As:

    // can be called with a Pair of As or sub-type of As
    void doWithPairOfA(const Pair<A, A> auto& p) { /* */ }
    

    Use case [b] - accept any valid pair, no restrictions on the inner types:

    void doWithAnyPair(const Pair<Any, Any> auto& p) { /* */ }
    

    Code: https://godbolt.org/z/higX9f