una plantilla pedidos pedido orden hacer hace formato ejemplo control compra como clientes automatizar c++ templates language-lawyer overload-resolution partial-ordering

c++ - pedidos - plantilla access orden de compra



Orden parcial de plantilla: ¿por qué la deducción parcial tiene éxito aquí? (2)

Como se discutió en los comentarios, creo que hay varios aspectos del algoritmo de ordenamiento parcial de plantilla de función que no están claros o no se especifican en absoluto en el estándar, y esto se muestra en su ejemplo.

Para hacer las cosas aún más interesantes, MSVC (probé 12 y 14) rechaza la llamada como ambigua. No creo que haya nada en el estándar que demuestre de manera concluyente qué compilador es el correcto, pero creo que podría tener una idea de dónde proviene la diferencia; hay una nota sobre eso a continuación.

Su pregunta (y esta ) me desafió a investigar un poco más sobre cómo funcionan las cosas. Decidí escribir esta respuesta no porque lo considere autoritario, sino para organizar la información que he encontrado en un lugar (no cabe en los comentarios). Espero que te sea útil.

Primero, la resolución propuesta para el problema 1391 . Lo discutimos extensamente en comentarios y chat. Creo que, si bien proporciona algunas aclaraciones, también presenta algunos problemas. Cambia [14.8.2.4p4] a (nuevo texto en negrita):

Cada tipo designado anteriormente de la plantilla de parámetros y el tipo correspondiente de la plantilla de argumento se utilizan como los tipos de P y A Si una P particular no contiene parámetros de plantilla que participen en la deducción de argumentos de plantilla, esa P no se usa para determinar el orden.

No es una buena idea en mi opinión, por varias razones:

  • Si P no es dependiente, no contiene ningún parámetro de plantilla, por lo que tampoco contiene ninguno que participe en la deducción de argumentos, lo que haría que se aplicara la declaración en negrita. Sin embargo, eso haría que la template<class T> f(T, int) y la template<class T, class U> f(T, U) no estén ordenadas, lo que no tiene sentido. Esto es posiblemente una cuestión de interpretación de la redacción, pero podría causar confusión.
  • Se mete con la noción de utilizado para determinar el orden , lo que afecta [14.8.2.4p11]. Esto hace que la template<class T> void f(T) y la template<class T> void f(typename A<T>::a) desordenada (la deducción tiene éxito de primera a segunda, porque T no se usa en un tipo usado para parcial ordenar de acuerdo con la nueva regla, por lo que puede permanecer sin un valor). Actualmente, todos los compiladores que he probado informan que el segundo es más especializado.
  • Haría el #2 más especializado que el #1 en el siguiente ejemplo:

    #include <iostream> template<class T> struct A { using a = T; }; struct D { }; template<class T> struct B { B() = default; B(D) { } }; template<class T> struct C { C() = default; C(D) { } }; template<class T> void f(T, B<T>) { std::cout << "#1/n"; } // #1 template<class T> void f(T, C<typename A<T>::a>) { std::cout << "#2/n"; } // #2 int main() { f<int>(1, D()); }

    (El segundo parámetro del n #2 no se utiliza para el pedido parcial, por lo que la deducción tiene éxito del n #1 al n #2 pero no al revés). Actualmente, la llamada es ambigua, y posiblemente debería seguir siéndolo.

Después de observar la implementación de Clang del algoritmo de ordenamiento parcial, así es como creo que el texto estándar podría cambiarse para reflejar lo que realmente sucede.

Deje [p4] como está y agregue lo siguiente entre [p8] y [p9]:

Para un par P / A :

  • Si P no es dependiente, la deducción se considera exitosa si y solo si P y A son del mismo tipo.
  • La sustitución de los parámetros de plantilla deducidos en los contextos no deducidos que aparecen en P no se realiza y no afecta el resultado del proceso de deducción.
  • Si los valores de argumento de plantilla se deducen con éxito para todos los parámetros de plantilla de P excepto los que aparecen solo en contextos no deducidos, entonces la deducción se considera exitosa (incluso si algunos parámetros utilizados en P permanecen sin un valor al final del proceso de deducción para ese par P / A particular).

Notas:

  • Sobre el segundo punto: [14.8.2.5p1] habla sobre encontrar valores de argumento de plantilla que harán que P , después de la sustitución de los valores deducidos (llámelo A deducido), sea compatible con A Esto podría causar confusión sobre lo que realmente sucede durante el pedido parcial; no hay sustitución en curso.
  • MSVC no parece implementar el tercer punto en algunos casos. Vea la siguiente sección para más detalles.
  • La segunda y tercera viñetas están destinadas a cubrir también casos en los que P tiene formas como A<T, typename U::b> , que no están cubiertas por la redacción del número 1391.

Cambie la [p10] actual a:

La plantilla de función F es al menos tan especializada como la plantilla de función G si y solo si:

  • Para cada par de tipos utilizados para determinar el orden, el tipo de F es al menos tan especializado como el tipo de G , y,
  • al realizar la deducción utilizando la F transformada como plantilla de argumento y G como plantilla de parámetro, después de realizar la deducción para todos los pares de tipos, todos los parámetros de plantilla utilizados en los tipos de G que se utilizan para determinar el orden tienen valores, y esos valores son consistentes en todos los pares de tipos.

F es más especializado que G si F es al menos tan especializado como G y G no es al menos tan especializado como F

Haga que toda la corriente [p11] sea una nota.

(La nota añadida por la resolución de 1391 a [14.8.2.5p4] también debe ajustarse; está bien para [14.8.2.1], pero no para [14.8.2.4]).

Para MSVC, en algunos casos, parece que todos los parámetros de plantilla en P necesitan recibir valores durante la deducción para ese par P / A específico para que la deducción tenga éxito de A a P Creo que esto podría ser lo que causa la divergencia de implementación en su ejemplo y en otros, pero he visto al menos un caso en el que lo anterior no parece aplicarse, por lo que no estoy seguro de qué creer.

Otro ejemplo en el que la declaración anterior parece aplicarse: al cambiar la template<typename T> void bar(T, T) a template<typename T, typename U> void bar(T, U) en su ejemplo se intercambian los resultados: la llamada es ambiguo en Clang y GCC, pero resuelve b en MSVC.

Un ejemplo donde no lo hace:

#include <iostream> template<class T> struct A { using a = T; }; template<class, class> struct B { }; template<class T, class U> void f(B<U, T>) { std::cout << "#1/n"; } template<class T, class U> void f(B<U, typename A<T>::a>) { std::cout << "#2/n"; } int main() { f<int>(B<int, int>()); }

Esto selecciona #2 en Clang y GCC, como se esperaba, pero MSVC rechaza la llamada como ambigua; No tengo idea de por qué.

El algoritmo de ordenamiento parcial como se describe en el estándar habla de sintetizar un tipo, un valor o una plantilla de clase únicos para generar los argumentos. Clang logra eso ... sin sintetizar nada. Solo usa las formas originales de los tipos dependientes (como se declara) y las combina en ambos sentidos. Esto tiene sentido, ya que la sustitución de los tipos sintetizados no agrega ninguna información nueva. No puede cambiar las formas de los tipos A , ya que generalmente no hay forma de saber a qué tipos concretos podrían resolver las formas sustituidas. Los tipos sintetizados son desconocidos, lo que los hace bastante similares a los parámetros de la plantilla.

Cuando se encuentra un P que es un contexto no deducido, el algoritmo de deducción de argumentos de plantilla de Clang simplemente lo omite, al devolver "éxito" para ese paso en particular. Esto sucede no solo durante el pedido parcial, sino también para todos los tipos de deducciones, y no solo en el nivel superior de una lista de parámetros de función, sino de forma recursiva cada vez que se encuentra un contexto no deducido en forma de un tipo compuesto. Por alguna razón, me pareció sorprendente la primera vez que lo vi. Pensando en ello, tiene sentido, por supuesto, y está de acuerdo con el estándar ( [...] no participa en la deducción [...] de tipo en [14.8.2.5p4]).

Esto es coherente con los comentarios de Richard Corden a su respuesta , pero tuve que ver el código del compilador para comprender todas las implicaciones (no es un error de su respuesta, sino de mi propio programador pensando en el código y todo eso).

He incluido más información sobre la implementación de Clang en esta respuesta .

Considere el siguiente ejemplo simple (en la medida en que las preguntas de plantilla son siempre):

#include <iostream> template <typename T> struct identity; template <> struct identity<int> { using type = int; }; template<typename T> void bar(T, T ) { std::cout << "a/n"; } template<typename T> void bar(T, typename identity<T>::type) { std::cout << "b/n"; } int main () { bar(0, 0); }

Tanto clang como gcc imprimen "a" allí. De acuerdo con las reglas en [temp.deduct.partial] y [temp.func.order], para determinar el orden parcial, necesitamos sintetizar algunos tipos únicos. Entonces tenemos dos intentos de deducción:

+---+-------------------------------+-------------------------------------------+ | | Parameters | Arguments | +---+-------------------------------+-------------------------------------------+ | a | T, typename identity<T>::type | UniqueA, UniqueA | | b | T, T | UniqueB, typename identity<UniqueB>::type | +---+-------------------------------+-------------------------------------------+

Para la deducción en "b", según la respuesta de Richard Corden , la expresión typename identity<UniqueB>::type se trata como un tipo y no se evalúa. Es decir, esto se sintetizará como si fuera:

+---+-------------------------------+--------------------+ | | Parameters | Arguments | +---+-------------------------------+--------------------+ | a | T, typename identity<T>::type | UniqueA, UniqueA | | b | T, T | UniqueB, UniqueB_2 | +---+-------------------------------+--------------------+

Está claro que la deducción en "b" falla. Esos son dos tipos diferentes, por lo que no puede deducir T a ambos.

Sin embargo, me parece que la deducción en A debería fallar. Para el primer argumento, coincidirías con T == UniqueA . El segundo argumento es un contexto no deducido, entonces, ¿no sería exitosa esa deducción si UniqueA fuera convertible a identity<UniqueA>::type ? El último es un fracaso de sustitución, por lo que tampoco veo cómo esta deducción podría tener éxito.

¿Cómo y por qué gcc y clang prefieren la sobrecarga "a" en este escenario?


Creo que la clave está en la siguiente declaración:

El segundo argumento es un contexto no deducido, entonces, ¿no sería exitosa esa deducción si UniqueA fuera convertible a identity :: type?

La deducción de tipo no realiza la comprobación de "conversiones". Esas comprobaciones tienen lugar utilizando los argumentos reales explícitos y deducidos como parte de la resolución de sobrecarga.

Este es mi resumen de los pasos que se toman para seleccionar la plantilla de función a llamar (todas las referencias tomadas de N3937, ~ C ++ ''14):

  1. Los argumentos explícitos se reemplazan y el tipo de función resultante verifica que sea válido. (14.8.2 / 2)
  2. Se realiza la deducción de tipo y se reemplazan los argumentos deducidos resultantes. Nuevamente, el tipo resultante debe ser válido. (14.8.2 / 5)
  3. Las plantillas de función que tuvieron éxito en los Pasos 1 y 2 están especializadas e incluidas en el conjunto de sobrecarga para la resolución de sobrecarga. (14.8.3 / 1)
  4. Las secuencias de conversión se comparan por resolución de sobrecarga. (13.3.3)
  5. Si las secuencias de conversión de dos especializaciones de funciones no son "mejores", se utiliza el algoritmo de ordenamiento parcial para encontrar la plantilla de funciones más especializada. (13.3.3)
  6. El algoritmo de orden parcial verifica solo que la deducción de tipo tenga éxito. (14.5.6.2/2)

El compilador ya sabe en el paso 4 que se pueden invocar ambas especializaciones cuando se usan los argumentos reales. Los pasos 5 y 6 se utilizan para determinar cuál de las funciones es más especializada.