Interessante Frage. Ich habe kürzlich Andrew Suttons Vortrag über Konzepte gesehen und in der Q & A-Sitzung hat jemand die folgende Frage gestellt (Zeitstempel im folgenden Link):
CppCon 2018: Andrew Sutton „Konzepte in 60: Alles, was Sie wissen müssen und nichts, was Sie nicht wissen“
Die Frage läuft also darauf hinaus: If I have a concept that says A && B && C, another says C && B && A, would those be equivalent?
Andrew antwortete mit Ja, wies jedoch darauf hin, dass der Compiler über einige interne Methoden verfügt (die für den Benutzer transparent sind), um die Konzepte in atomare logische Sätze zu zerlegen ( atomic constraints
wie Andrew den Begriff formulierte) und zu prüfen, ob dies der Fall ist Äquivalent.
Schauen Sie sich nun an, was cppreference über Folgendes sagt std::same_as
:
std::same_as<T, U>
subsumiert std::same_as<U, T>
und umgekehrt.
Es ist im Grunde eine "Wenn-und-Nur-Wenn" -Beziehung: Sie implizieren sich gegenseitig. (Logische Äquivalenz)
Meine Vermutung ist, dass hier die atomaren Zwänge sind std::is_same_v<T, U>
. Die Art und Weise, wie Compiler damit umgehen, std::is_same_v
könnte sie zum Nachdenken anregen std::is_same_v<T, U>
und std::is_same_v<U, T>
als zwei verschiedene Einschränkungen (sie sind verschiedene Einheiten!). Wenn Sie also std::same_as
nur eine davon implementieren :
template< class T, class U >
concept same_as = detail::SameHelper<T, U>;
Dann std::same_as<T, U>
und std::same_as<U, T>
würde zu verschiedenen atomaren Zwängen "explodieren" und nicht gleichwertig werden.
Warum kümmert es den Compiler?
Betrachten Sie dieses Beispiel :
#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;
}
Idealerweise my_same_as<T, U> && std::integral<T>
subsumiert my_same_as<U, T>
; Daher sollte der Compiler die zweite Vorlagenspezialisierung auswählen, außer ... nicht: Der Compiler gibt einen Fehler aus error: call of overloaded 'foo(int, int)' is ambiguous
.
Der Grund dafür ist , dass da my_same_as<U, T>
und my_same_as<T, U>
nicht sie nicht subsumieren, my_same_as<T, U> && std::integral<T>
und my_same_as<U, T>
unvergleichlich werden (auf dem teilweise geordneten Satz von Einschränkungen nach der Beziehung von Subsumtion).
Wenn Sie jedoch ersetzen
template< class T, class U >
concept my_same_as = SameHelper<T, U>;
mit
template< class T, class U >
concept my_same_as = SameHelper<T, U> && SameHelper<U, T>;
Der Code wird kompiliert.
SameHelper<T, U>
es wahr sein könnte, heißt das nicht, dass es wahr seinSameHelper<U, T>
könnte.