Интересный вопрос. Недавно я смотрел доклад Эндрю Саттона о концепциях, и в ходе сессии вопросов и ответов кто-то задал следующий вопрос (отметка времени в следующей ссылке): CppCon 2018: Эндрю Саттон «Концепции в 60: все, что вам нужно знать, и ничего, что вы не делаете»'t ”
Таким образом, вопрос сводится к следующему: 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>
subumsstd::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>;
Код скомпилируется.