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) { /* */ }
Can concepts present a better solution?
1 Related question (pre C++20) on templates: Templates accepting “anything” in C++
type_matches
applies equally toPair
:concept Pair = type_matches<typename P::first_type, First> && type_matches<typename P::second_type, Second>;
– Langrage