Да, &&
и ||
здесь рассматриваются как особые, потому что ограничения учитывают конъюнкции и дизъюнкции.
§ 13.5.1 Ограничения [temp.constr.constr]
Ограничение - это последовательность логических операций и операндов, определяющая требования к аргументам шаблона. Операнды логической операции - это ограничения. Есть три разных вида ограничений:
(1.1) - союзы (13.5.1.1), (1.2) - дизъюнкции (13.5.1.1), и (1.3) - ограничения atomi c (13.5.1.2).
Они должны быть, чтобы определить частичное упорядочение по ограничениям.
13.5.4 Частичное упорядочение по ограничениям [temp .constr.order]
[Примечание: [...] Этот частичный порядок используется для определения
- (2.1) наилучшего жизнеспособного кандидата для нешаблонных функций (12.4.3),
- (2.2) адрес нешаблонной функции (12.5),
- (2.3) сопоставление аргументов шаблона шаблона (13.4.3),
- (2.4) частичное упорядочение специализаций шаблонов классов (13.7.5.2) и
- (2.5) частичное упорядочение шаблонов функций (13.7.6.2).
- конец примечания]
Что заставляет этот код работать:
template <class It>
concept bidirectional_iterator = requires /*...*/;
template <class It>
concept randomaccess_iterator = bidirectional_iterator<It> && requires /*...*/;
template <bidirectional_iterator It>
void sort(It begin, It end); // #1
template <randomaccess_iterator It>
void sort(It begin, It end); // #2
std::list<int> l{};
sort(l.begin(), l.end()); // #A -> calls #1
std::vector<int> v{};
sort(v.begin(), v.end()); // #B -> calls #2
Но для вызова #B
, даже если оба sort
являются жизнеспособными, поскольку оба ограничения (randomaccess_iterator
и bidirectional_iterator
удовлетворены) sort #2
(тот, у которого randomaccess_iterator
) выбран правильно, потому что он более ограничен , чем sort #1
(тот, у которого bidirectional_iterator
), потому что randomaccess_iterator
включает в себя bidirectional_iterator
:
См. Как с концепциями выбирается лучший шаблон ограниченной функции?