c++ c++20 concept

c++ - ¿Por qué el mismo concepto verifica la igualdad de tipos dos veces?



c++20 (2)

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 atomic constraints ( atomic constraints como Andrew redactó el término) y verificar si son equivalentes.

Ahora mire lo que dice cppreference sobre std::same_as :

std::same_as<T, U> subsume std::same_as<U, T> y viceversa.

Básicamente es una relación "si-y-solo-si": se implican mutuamente. (Equivalencia lógica)

Mi conjetura es que aquí las restricciones atómicas son std::is_same_v<T, U> . La forma en que los compiladores tratan std::is_same_v podría hacerles pensar std::is_same_v<T, U> y std::is_same_v<U, T> como dos restricciones diferentes (¡son entidades diferentes!). Entonces, si implementa std::same_as usando solo uno de ellos:

template< class T, class U > concept same_as = detail::SameHelper<T, U>;

Entonces std::same_as<T, U> y std::same_as<U, T> "explotarían" a diferentes restricciones atómicas y no serían equivalentes.

Bueno, ¿por qué le importa al compilador?

Considere este ejemplo :

#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; }

Idealmente, my_same_as<T, U> && std::integral<T> subsume my_same_as<U, T> ; por lo tanto, el compilador debe seleccionar la segunda especialización de plantilla, excepto que ... no lo hace: el compilador emite un error de error: call of overloaded ''foo(int, int)'' is ambiguous .

La razón detrás de esto es que ya que my_same_as<U, T> y my_same_as<T, U> no se subsumen entre sí, my_same_as<T, U> && std::integral<T> y my_same_as<U, T> vuelven incomparables ( en el conjunto de restricciones parcialmente ordenadas bajo la relación de subsunción).

Sin embargo, si reemplaza

template< class T, class U > concept my_same_as = SameHelper<T, U>;

con

template< class T, class U > concept my_same_as = SameHelper<T, U> && SameHelper<U, T>;

El código se compila.

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 concepto SameHelper está incorporado? El segundo es por qué same_as comprueba si T es igual a U y U igual que T ? ¿No es redundante?


std::is_same se define como verdadero si y solo si:

T y U nombran el mismo tipo con las mismas calificaciones de CV

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 is_same_v<T, U> && is_same_v<U, V> , is_same_v<T, U> && is_same_v<U, V> sería de hecho redundante. Pero same_as no se especifica en términos de is_same_v ; eso es solo para exposición.

La verificación explícita de ambos permite la implementación de same-as-impl para satisfacer same_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.

No sé exactamente por qué se eligió este enfoque en lugar de especificarlo en términos de is_same_v . Una ventaja del enfoque elegido es posiblemente que las dos definiciones están desacopladas. Uno no depende del otro.