Да concepts
предназначены для этой цели. Если отправленный параметр не соответствует требуемому концептуальному аргументу, функция не будет учитываться в списке разрешения перегрузки, что позволит избежать двусмысленности.
Более того, если отправленный параметр соответствует нескольким функциям, будет выбрана более конкретная.
Простой пример:
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 :: integers auto) неограниченному типу (просто auto). Но правила также применимы к двум конкурирующим концепциям. Компилятор должен выбрать более конкретный, если он более конкретный. Конечно, если обе концепции соблюдены и ни одна из них не является более конкретной, это приведет к двусмысленности.
Что же делает концепцию более конкретной? если он основан на другом 1.
Общая концепция - GenericTwople:
template<class P>
concept GenericTwople = requires(P p) {
requires std::tuple_size<P>::value == 2;
std::get<0>(p);
std::get<1>(p);
};
Более конкретная концепция - Двое:
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, поэтому он более конкретен.
Если вы замените в нашем Twople строку:
GenericTwople<P> && // <= note this line
с фактическими требованиями, предъявляемыми этой строкой, Twople по-прежнему будет иметь те же требования, но больше не будет более конкретным, чем 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)
Мы можем пойти дальше, поскольку представленная выше концепция 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 не допускает концептуальную специализацию, иначе мы пошли бы еще дальше:
template<class P>
concept Twople<P, Any, Any> = GenericTwople<P>;
Что могло бы добавить хороший возможный ответ на этот SO вопрос, однако специализация понятий не допускается.
1 Фактические правила для частичного упорядочивания ограничений более сложны, см .: cppreference / Спецификация C ++ 20.
person
Amir Kirsh
schedule
26.02.2020