¿Cómo implementar la forma generalizada de std :: same_as (es decir, para más de dos parámetros de tipo) que es independiente del orden de los parámetros?

8

Antecedentes

Sabemos que el concepto std::same_ases agnóstico al orden (en otras palabras, simétrico): std::same_as<T, U>es equivalente a std::same_as<U, T>( pregunta relacionada ). En esta pregunta, me gustaría implementar algo más general: template <typename ... Types> concept same_are = ...que verifique si los tipos en el paquete Typesson iguales entre sí.

Mi intento

#include <type_traits>
#include <iostream>
#include <concepts>

template <typename T, typename... Others>
concept same_with_others = (... && std::same_as<T, Others>);

template <typename... Types>
concept are_same = (... && same_with_others<Types, Types...>);

template< class T, class U> requires are_same<T, U>
void foo(T a, U b) {
    std::cout << "Not integral" << std::endl;
}

// Note the order <U, T> is intentional
template< class T, class U> requires (are_same<U, T> && std::integral<T>)
void foo(T a, U b) {
    std::cout << "Integral" << std::endl;
}

int main() {
    foo(1, 2);
    return 0;
}

(Mi intención aquí es enumerar todos los pares de tipos ordenados posibles en el paquete)

Desafortunadamente, este código no se compilaría , ya que el compilador se queja de que la llamada a foo(int, int)es ambigua. Creo que lo considera are_same<U, T>y are_same<T, U>como no equivalente. Me gustaría saber por qué el código falla y cómo puedo solucionarlo (para que el compilador los trate como equivalentes).

Rin Kaenbyou
fuente
Mi instinto me dice que necesitará un ayudante que se ejecute same_with_othersen cada permutación posible de los tipos.
StoryTeller - Unslander Monica
No estoy completamente seguro si te entiendo correctamente. ¿Quieres comprobar si todos los tipos ... Typesson iguales? Tal vez std :: conjunción pueda ayudarte. Hay un ejemplo en la parte inferior de la página que parece similar a su enfoque.
churill
@ StoryTeller-UnslanderMonica Pero ya he enumerado todos los posibles pares de tipos ordenados en el paquete. ¿No es eso suficiente? ¿O los compiladores no pueden determinar la equivalencia de pliegues sin ningún tipo concreto?
Rin Kaenbyou, el
@churill Me refiero a implementar esto en conceptos, y la ordenación de parámetros necesita un cuidado especial en los conceptos.
Rin Kaenbyou, el
No estoy seguro, por lo tanto, es solo un presentimiento. Podría ser que los desarrolladores de GCC aún no estén seguros. También podría ser que aún no lo hayan implementado completamente.
StoryTeller - Unslander Monica

Respuestas:

5

El problema es, con este concepto:

template <typename T, typename... Others>
concept are_same = (... && std::same_as<T, Others>);

Es que la forma normalizada de este concepto es ... exactamente eso. No podemos "desplegar" esto (no hay nada que hacer), y las reglas actuales no se normalizan a través de "partes" de un concepto.

En otras palabras, lo que necesita para que esto funcione es que su concepto se normalice en:

... && (same-as-impl<T, U> && same-as-impl<U, T>)

dentro:

... && (is_same_v<T, U> && is_same_v<U, T>)

Y considere una &&restricción de expresión de pliegue para subsumir otra restricción de expresión de pliegue &&si su restricción subyacente subsume la restricción subyacente del otro. Si tuviéramos esa regla, eso haría que tu ejemplo funcionara.

Es posible agregar esto en el futuro, pero la preocupación en torno a las reglas de subsunción es que no queremos exigir que los compiladores hagan todo lo posible e implementen un solucionador SAT completo para verificar la subsunción de restricciones. Este no parece que lo haga mucho más complicado (realmente solo agregaríamos las reglas &&y ||mediante expresiones de pliegue), pero realmente no tengo idea.

Sin embargo, tenga en cuenta que incluso si tuviéramos este tipo de subsunción de expresión doble, are_same<T, U>aún no subsumiría std::same_as<T, U>. Solo subsumiría are_same<U, T>. No estoy seguro de si esto sería posible.

Barry
fuente
2
Me sorprende que esto no funcione. Es razonable que solo los conceptos puedan subsumirse. Sin embargo, en mi opinión, (... && C<T>)no subsumir el concepto C<T> sorprendería a muchos usuarios.
metalfox
@metalfox: desde mi lectura de la normalización, su ejemplo debería estar bien (el uso de restricciones funciona explícitamente a través de Demo ).
Jarod42
@ Jarod42 Lo que escribiste y lo que escribió metalfox no es lo mismo: la diferencia es de lo que está hablando metalfox.
Barry
@ Jarod42 Sí, eso funciona porque los conceptos participan en la subsunción de restricción , y usted ha materializado la expresión de pliegue (que involucra conceptos) en un solo concepto. Desafortunadamente, como indicó en su respuesta, las expresiones de plegado no están normalizadas en los conceptos de los que están hechas. Esto tampoco funciona: godbolt.org/z/pjmKxR
metalfox
Podría haber entendido mal la restricción de la normalización entonces: - / Entiendo ((fold1<Ts> && ...) && (fold2<Ts> &&...))como conjunción de (fold1<Ts> && ...)y (fold2<Ts> && ...)mientras que es atómica.
Jarod42
5

De cppreference.com Restricción_normalización

La forma normal de cualquier otra expresión E es la restricción atómica cuya expresión es E y cuyo mapeo de parámetros es el mapeo de identidad. Esto incluye todas las expresiones de plegado, incluso aquellas plegadas sobre && o || operadores.

Entonces

template <typename... Types>
concept are_same = (... && same_with_others<Types, Types...>);

es "atómico"

De hecho, are_same<U, T>y are_same<T, U>no son equivalentes.

No veo cómo implementarlo :-(

Jarod42
fuente