Интересный вопрос. Недавно я смотрел доклад Эндрю Саттона о концепциях, и в ходе сессии вопросов и ответов кто-то задал следующий вопрос (временная метка в следующей ссылке):
CppCon 2018: Эндрю Саттон «Концепции в 60: все, что вам нужно знать, и ничего, чего вы не делаете»
Таким образом, вопрос сводится к следующему: If I have a concept that says A && B && C, another says C && B && A, would those be equivalent?
Эндрю ответил да, но указал на тот факт, что компилятор имеет некоторые внутренние методы (которые прозрачны для пользователя), чтобы разложить понятия на атомарные логические предложения ( atomic constraints
как Эндрю сформулировал термин) и проверить, являются ли они эквивалент.
Теперь посмотрим, что говорит cppreference std::same_as
:
std::same_as<T, U>
включает в себя std::same_as<U, T>
и наоборот.
Это в основном отношения «если и только если»: они подразумевают друг друга. (Логическая эквивалентность)
Моя гипотеза состоит в том, что здесь атомные ограничения std::is_same_v<T, U>
. То, как обрабатывают компиляторы, std::is_same_v
может заставить их думать std::is_same_v<T, U>
и std::is_same_v<U, T>
как два разных ограничения (это разные сущности!). Так что если вы реализуете, std::same_as
используя только один из них:
template< class T, class U >
concept same_as = detail::SameHelper<T, U>;
Тогда std::same_as<T, U>
иstd::same_as<U, T>
будет «взорваться» к различным атомным ограничениям и стать не эквивалентны.
Ну, а зачем это компилятору?
Рассмотрим этот пример :
#include <type_traits>
#include <iostream>
#include <concepts>
template< class T, class U >
concept SameHelper = std::is_same_v<T, U>;
template< class T, class U >
concept my_same_as = SameHelper<T, U>;
// template< class T, class U >
// concept my_same_as = SameHelper<T, U> && SameHelper<U, T>;
template< class T, class U> requires my_same_as<U, T>
void foo(T a, U b) {
std::cout << "Not integral" << std::endl;
}
template< class T, class U> requires (my_same_as<T, U> && std::integral<T>)
void foo(T a, U b) {
std::cout << "Integral" << std::endl;
}
int main() {
foo(1, 2);
return 0;
}
В идеале, my_same_as<T, U> && std::integral<T>
подгруппы my_same_as<U, T>
; следовательно, компилятор должен выбрать вторую специализацию шаблона, кроме ... это не так: компилятор выдает ошибкуerror: call of overloaded 'foo(int, int)' is ambiguous
.
Причиной этого является то, что, так как my_same_as<U, T>
и my_same_as<T, U>
не включает друг друга, my_same_as<T, U> && std::integral<T>
иmy_same_as<U, T>
становятся несопоставимыми (на частично упорядоченном множестве ограничений по отношению к подчинению).
Однако, если вы замените
template< class T, class U >
concept my_same_as = SameHelper<T, U>;
с
template< class T, class U >
concept my_same_as = SameHelper<T, U> && SameHelper<U, T>;
Код компилируется.
SameHelper<T, U>
может быть правдой, не значит,SameHelper<U, T>
может быть.