Al observar la posible implementación del concepto same_as en https://en.cppreference.com/w/cpp/concepts/same_as noté que algo extraño está sucediendo.
namespace detail {
template< class T, class U >
concept SameHelper = std::is_same_v<T, U>;
}
template< class T, class U >
concept same_as = detail::SameHelper<T, U> && detail::SameHelper<U, T>;
La primera pregunta es ¿por qué un SameHelper
concepto está integrado? La segunda es ¿por qué same_as
comprueba si T
es igual U
o U
igual que T
? ¿No es redundante?
SameHelper<T, U>
podría ser cierto no significa queSameHelper<U, T>
podría serlo.is_same<T, U>::value == true
si y solo siis_same<U, T>::value == true
". Esto implica que esta doble verificación no es necesariaRespuestas:
Interesante pregunta. Recientemente vi la charla de Andrew Sutton sobre conceptos, y en la sesión de preguntas y respuestas alguien hizo la siguiente pregunta (marca de tiempo en el siguiente enlace): CppCon 2018: Andrew Sutton "Conceptos en 60: todo lo que necesita saber y nada que no"
Entonces la pregunta se reduce a:
If I have a concept that says A && B && C, another says C && B && A, would those be equivalent?
Andrew respondió que sí, pero señaló el hecho de que el compilador tiene algunos métodos internos (que son transparentes para el usuario) para descomponer los conceptos en proposiciones lógicas atómicas (atomic constraints
como Andrew redactó el término) y verificar si son equivalente.Ahora mira lo que dice cppreference sobre
std::same_as
:Básicamente es una relación "si-y-solo-si": se implican mutuamente. (Equivalencia lógica)
Mi conjetura es que aquí están las restricciones atómicas
std::is_same_v<T, U>
. La forma en que los compiladores tratanstd::is_same_v
puede hacerlos pensarstd::is_same_v<T, U>
ystd::is_same_v<U, T>
como dos restricciones diferentes (¡son entidades diferentes!). Entonces, si implementastd::same_as
usando solo uno de ellos:Entonces
std::same_as<T, U>
ystd::same_as<U, T>
"explotaría" a diferentes restricciones atómicas y no sería equivalente.Bueno, ¿por qué le importa al compilador?
Considere este ejemplo :
Idealmente,
my_same_as<T, U> && std::integral<T>
subsumesmy_same_as<U, T>
; por lo tanto, el compilador debe seleccionar la segunda especialización de plantilla, excepto ... no lo hace: el compilador emite un errorerror: call of overloaded 'foo(int, int)' is ambiguous
.La razón detrás de esto es que, dado que
my_same_as<U, T>
ymy_same_as<T, U>
no subsume entre sí,my_same_as<T, U> && std::integral<T>
ymy_same_as<U, T>
se convierten incomparable (en el conjunto parcialmente ordenado de restricciones bajo la relación de subsunción).Sin embargo, si reemplaza
con
El código se compila.
fuente
SameHelper
: hace que los dos usos deis_same_v
deriven de la misma expresión.is_same<T, U>
es idénticois_same<U, T>
, dos restricciones atómicas no se consideran idénticas a menos que también se formen a partir de la misma expresión. De ahí la necesidad de ambos.are_same_as
?template<typename T, typename U0, typename... Un> concept are_same_as = SameAs<T, U0> && (SameAs<T, Un> && ...);
fallaría en algunos casos. Por ejemploare_same_as<T, U, int>
, sería equivalenteare_same_as<T, int, U>
pero no aare_same_as<U, T, int>
std::is_same
se define como verdadero si y solo si:Hasta donde sé, estándar no define el significado de "mismo tipo", pero en lenguaje natural y lógica "mismo" es una relación de equivalencia y, por lo tanto, es conmutativa.
Dado este supuesto, al que atribuyo,
is_same_v<T, U> && is_same_v<U, V>
sería redundante. Perosame_as
no se especifica en términos deis_same_v
; eso es solo para exposición.La verificación explícita de ambos permite la implementación para
same-as-impl
satisfacersame_as
sin ser conmutativo. Al especificarlo de esta manera se describe exactamente cómo se comporta el concepto sin restringir cómo se podría implementar.is_same_v
No sé exactamente por qué se eligió este enfoque en lugar de especificarlo . Una ventaja del enfoque elegido es posiblemente que las dos definiciones están desacopladas. Uno no depende del otro.fuente