问题
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, TestAgainst>
concept derived_from_or_same_as =
std::same_as<Me, TestAgainst> ||
std::derived_from<decltype(p.first), First>;
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 not on concepts): Templates accepting "anything" in C++
回答1:
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
来源:https://stackoverflow.com/questions/60400537/wildcard-for-c-concepts-saying-accepting-anything-for-this-template-argument