Цікаве запитання. Нещодавно я спостерігав, як Ендрю Саттон розмовляв з «Концептами», і на сесії запитань і запитань хтось задав таке запитання (мітка часу за наступним посиланням):
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>
може бути.