Да concepts
предназначены для этой цели. Если отправленный параметр не соответствует требуемому концептуальному аргументу, функция не будет учитываться в списке разрешений перегрузки, что позволяет избежать неоднозначности.
Более того, если отправленный параметр соответствует нескольким функциям, тем больше специфика c будет выбран один.
Простой пример:
void print(auto t) {
std::cout << t << std::endl;
}
void print(std::integral auto i) {
std::cout << "integral: " << i << std::endl;
}
Выше print
функции являются допустимой перегрузкой, которая может существовать вместе.
- Если мы отправим нецелый тип, он выберет первый
- Если мы отправим целочисленный тип, он предпочтет второй
, например, вызов функции:
print("hello"); // calls print(auto)
print(7); // calls print(std::integral auto)
Нет двусмысленности - две функции могут прекрасно жить вместе, бок о бок.
Нет необходимости в каком-либо коде SFINAE , например enable_if
- он уже применяется (очень хорошо спрятан).
Выбор между двумя концептами
В приведенном выше примере показано, как компилятор предпочитает тип с ограничениями ( std: : встроенное авто ) по типу без ограничений ( просто авто ). Но правила также применяются к двум конкурирующим понятиям. Компилятор должен выбрать более конкретный c, если он более конкретный c. Конечно, если оба понятия соблюдаются и ни одно из них не является более конкретным c, это приведет к неоднозначности.
Что же делает концепцию более конкретным c? если он основан на другом 1 .
Концепция generi c - GenericTwople :
template<class P>
concept GenericTwople = requires(P p) {
requires std::tuple_size<P>::value == 2;
std::get<0>(p);
std::get<1>(p);
};
Чем больше специфика c concept - Twople:
class Any;
template<class Me, class TestAgainst>
concept type_matches =
std::same_as<TestAgainst, Any> ||
std::same_as<Me, TestAgainst> ||
std::derived_from<Me, TestAgainst>;
template<class P, class First, class Second>
concept Twople =
GenericTwople<P> && // <= note this line
type_matches<std::tuple_element_t<0, P>, First> &&
type_matches<std::tuple_element_t<1, P>, Second>;
Обратите внимание, что Twople требуется для удовлетворения требований GenericTwople, поэтому он более конкретен c.
Если вы замените в нашем Twople строку:
GenericTwople<P> && // <= note this line
с учетом фактических требований, предъявляемых к этой строке, Twople будет по-прежнему иметь те же требования, но более не будет более точным c, чем GenericTwople. Это, конечно же, наряду с повторным использованием кода, поэтому мы предпочитаем определять Twople на основе GenericTwople.
Теперь мы можем играть со всеми видами перегрузок:
void print(auto t) {
cout << t << endl;
}
void print(const GenericTwople auto& p) {
cout << "GenericTwople: " << std::get<0>(p) << ", " << std::get<1>(p) << endl;
}
void print(const Twople<int, int> auto& p) {
cout << "{int, int}: " << std::get<0>(p) << ", " << std::get<1>(p) << endl;
}
И вызывать это с:
print(std::tuple{1, 2}); // goes to print(Twople<int, int>)
print(std::tuple{1, "two"}); // goes to print(GenericTwople)
print(std::pair{"three", 4}); // goes to print(GenericTwople)
print(std::array{5, 6}); // goes to print(Twople<int, int>)
print("hello"); // goes to print(auto)
Мы можем go дальше, поскольку представленная выше концепция Twople также работает с полиморфизмом:
struct A{
virtual ~A() = default;
virtual std::ostream& print(std::ostream& out = std::cout) const {
return out << "A";
}
friend std::ostream& operator<<(std::ostream& out, const A& a) {
return a.print(out);
}
};
struct B: A{
std::ostream& print(std::ostream& out = std::cout) const override {
return out << "B";
}
};
добавить следующую перегрузку:
void print(const Twople<A, A> auto& p) {
cout << "{A, A}: " << std::get<0>(p) << ", " << std::get<1>(p) << endl;
}
и вызовите его (пока присутствуют все другие перегрузки) с помощью:
print(std::pair{B{}, A{}}); // calls the specific print(Twople<A, A>)
Код: https://godbolt.org/z/3-O1Gz
К сожалению C + +20 не допускает специализацию концепта, в противном случае мы бы go пошли еще дальше:
template<class P>
concept Twople<P, Any, Any> = GenericTwople<P>;
Что могло бы добавить хороший возможный ответ на этот вопрос SO , однако специализация концепта не допускается.
1 Фактические правила для Частичное упорядочение ограничений более сложные, см .: cppreference / C ++ 20 spe c.