template plantilla para metodos metodo index ejemplo crear controlador altas c++ templates c++11 ambiguous

c++ - plantilla - metodos controlador laravel



Orden parcial de plantillas de funciones: llamada ambigua (2)

Estoy publicando los detalles de mi comprensión actual del problema como una respuesta. No estoy seguro de que sea la última palabra sobre esto, pero podría servir como base para futuras discusiones si es necesario. Los comentarios de dyp, hvd y Columbo han sido esenciales para encontrar los diversos bits de información a los que se hace referencia a continuación.

Como sospechaba, el problema es con las reglas para el ordenamiento parcial de plantillas de funciones. La sección [14.8.2.4] ( Deducir argumentos de plantilla durante el ordenamiento parcial ) dice que, después de las transformaciones preliminares que eliminan referencias y cv-calificadores, la deducción de tipo se hace como se describe en [14.8.2.5] ( Deducir argumentos de plantilla de un tipo ). Esa sección es diferente de la que se refiere a llamadas de función, que sería [14.8.2.1] ( Deducir argumentos de plantilla de una llamada a función ).

Cuando los parámetros de plantilla se deducen de los tipos de argumento de función, hay unos pocos casos especiales que están permitidos; por ejemplo, un parámetro de plantilla T utilizado en un parámetro de función de tipo T* se puede deducir cuando el argumento de función es T[i] , porque la conversión de matriz a puntaje está permitida en este caso. Sin embargo, este no es el proceso de deducción que se utiliza durante el pedido parcial , a pesar de que todavía estamos hablando de funciones.

Supongo que la manera más fácil de pensar sobre las reglas para la deducción de argumentos de la plantilla durante el pedido parcial es decir que son las mismas reglas que para deducir los argumentos de la plantilla cuando se combinan las especializaciones de la plantilla de clase .

¿Claro como el barro? Tal vez un par de ejemplos ayuden.

Esto funciona, porque usa las reglas para deducir argumentos de plantilla de una llamada a función :

#include <iostream> #include <type_traits> template<typename T> void f(T*) { std::cout << std::is_same<T, int>::value << ''/n''; } int main() { int a[3]; f(a); }

e imprime 1 .

Esto no ocurre porque usa las reglas para deducir argumentos de plantilla de un tipo :

#include <iostream> template<typename T> struct A; template<typename T> struct A<T*> { static void f() { std::cout << "specialization/n"; } }; int main() { A<int[3]>::f(); }

y el error de Clang es

error: implicit instantiation of undefined template ''A<int [3]>''

La especialización no se puede usar, porque T* e int[3] no coinciden en este caso, por lo que el compilador intenta crear una instancia de la plantilla primaria.

Es este segundo tipo de deducción que se usa durante el pedido parcial.

Volvamos a nuestras declaraciones de plantillas de funciones:

template<typename T> void f(T, const char*); //#1 template<std::size_t N> void f(int, const char(&)[N]); //#2

Mi descripción del proceso para ordenar parcialmente se convierte en:

  • Deducción de argumento de plantilla con # 1 como parámetro y # 2 como argumento: inventamos un valor M para sustituir a N , T se deduce como int , pero un parámetro de tipo const char* no coincide con un argumento de tipo char[M] , así que # 2 no está al menos tan especializado como el # 1 para el segundo par de tipos.
  • Deducción de argumento de plantilla con # 2 como parámetro y # 1 como argumento: inventamos un tipo U para sustituir a T , int y U no coinciden (diferentes tipos), un parámetro de tipo char[N] no coincide con un argumento de tipo const char* , y el valor del parámetro de plantilla no de tipo N no se puede deducir de los argumentos, por lo que # 1 no es al menos tan especializado como el # 2 para ningún par de tipos.

Dado que, para ser elegido, una plantilla debe ser al menos tan especializada como la otra para todos los tipos, se deduce que ninguna plantilla es más especializada que la otra y la llamada es ambigua.

La explicación anterior va un poco en contra de la descripción de un problema similar en Core Language Active Issue 1610 (enlace proporcionado por hvd).

El ejemplo allí es:

template<class C> void foo(const C* val) {} template<int N> void foo(const char (&t)[N]) {}

El autor argumenta que, intuitivamente, la segunda plantilla debe elegirse como más especializada, y que esto no ocurre actualmente (ninguna plantilla es más especializada que la otra).

Luego explica que la razón es la eliminación del calificador const de const char[N] , produciendo char[N] , que hace que la deducción falle con const C* como parámetro.

Sin embargo, según mi comprensión actual, la deducción fallaría en este caso, const o no const . Esto es confirmado por las implementaciones actuales en Clang y GCC: si eliminamos el calificador const de los parámetros de ambas plantillas de funciones y llamamos a foo() con un argumento char[3] , la llamada sigue siendo ambigua. Las matrices y punteros simplemente no coinciden de acuerdo con las reglas actuales durante el pedido parcial.

Una vez dicho esto, no soy miembro del comité, por lo que puede haber más de lo que entiendo actualmente.

Actualización: Recientemente me encontré con otro problema activo de Core que data de 2003: número 402 .

El ejemplo allí es equivalente al de 1610 . Los comentarios sobre el tema dejan en claro que las dos sobrecargas no están ordenadas de acuerdo con el algoritmo de ordenamiento parcial tal como está, precisamente por la falta de reglas de decaimiento de matriz a puntero durante el pedido parcial.

El último comentario es:

Hubo algún sentimiento de que sería deseable ordenar este caso, pero no creemos que valga la pena gastar el tiempo para trabajar en él ahora. Si observamos algunos cambios de pedidos parciales más grandes en algún momento, lo consideraremos de nuevo.

Por lo tanto, estoy bastante seguro de que la interpretación que di más arriba es correcta.

Considere esta pieza del código C ++ 11:

#include <iostream> #include <cstddef> template<typename T> void f(T, const char*) //#1 { std::cout << "f(T, const char*)/n"; } template<std::size_t N> void f(int, const char(&)[N]) //#2 { std::cout << "f(int, const char (&)[N])/n"; } int main() { f(7, "ab"); }

Bien, entonces ... ¿qué sobrecarga se elige? Antes de derramar los beans con la salida del compilador, intentemos razonar sobre esto.

(Todas las referencias a las secciones corresponden al documento estándar final para C ++ 11, ISO / IEC 14882: 2011).

T de # 1 se deduce a int , N de # 2 se deduce a 3 , ambas especializaciones son candidatas, ambas son viables, hasta ahora todo bien. ¿Cuál es el mejor?

En primer lugar, se consideran las conversiones implícitas necesarias para unir los argumentos de función con los parámetros de función. Para el primer argumento, no se necesita conversión en ninguno de los casos ( conversión de identidad ), int todas partes, por lo que las dos funciones son igualmente buenas. Para el segundo, el tipo de argumento es const char[3] , y las dos conversiones son:

  • para # 1 , conversión de matriz a puntero , transformación de categoría lvalue , de acuerdo con [13.3.3.1.1] ; esta categoría de conversión se ignora al comparar secuencias de conversión de acuerdo con [13.3.3.2] , por lo que esto es básicamente lo mismo que la conversión de identidad para este propósito;
  • para # 2 , el parámetro es de tipo de referencia y se une directamente al argumento, por lo tanto, de acuerdo con [13.3.3.1.4] , esta es nuevamente la conversión de identidad .

Nuevamente, sin suerte: las dos funciones siguen siendo igual de buenas. Ambas son especializaciones de plantilla, ahora tenemos que ver qué plantilla de función, si hay alguna, es más especializada ( [14.5.6.2] y [14.8.2.4] ).

EDIT 3: La siguiente descripción está cerca, pero no es muy precisa. Vea mi respuesta para lo que creo que es la descripción correcta del proceso.

  • Deducción de argumento de plantilla con # 1 como parámetro y # 2 como argumento: inventamos un valor M para sustituir a N , T deducido como int , const char* como parámetro inicializable desde un argumento de tipo char[M] , todo está bien. Por lo que puedo decir, el # 2 es al menos tan especializado como el # 1 para todos los tipos involucrados.
  • Deducción de argumento de plantilla con # 2 como parámetro y # 1 como argumento: inventamos un tipo U para sustituir a T , un parámetro de tipo int no se puede inicializar a partir de un argumento de tipo U (tipos no relacionados), un parámetro de tipo char[N] no se puede inicializar desde un argumento de tipo const char* , y el valor del parámetro N tipo no se puede deducir de los argumentos, por lo que ... todo falla. Por lo que yo sé, el # 1 no es tan especializado como el # 2 para todos los tipos involucrados.

EDIT 1: Lo anterior se ha editado en base a los comentarios de Columbo y dyp, para reflejar el hecho de que las referencias se eliminan antes de intentar la deducción del argumento de la plantilla en este caso.

EDIT 2: Según la información de hvd, también se eliminan los cv-qualifiers de nivel superior. En este caso, significa que const char[N] convierte en char[N] , porque los cv-calificadores en los elementos de la matriz también se aplican a la matriz (una array of const también es una const array , por así decirlo); esto no era del todo evidente en el estándar C ++ 11, pero se ha aclarado para C ++ 14.

En base a lo anterior, diría que el ordenamiento parcial de las plantillas de funciones debería elegir el # 2 como más especializado, y la llamada debería resolverse sin ambigüedades.

Ahora, de vuelta a la dura realidad. Tanto GCC 4.9.1 como Clang 3.5.0, con las siguientes opciones

-Wall -Wextra -std=c++11 -pedantic

rechaza la llamada como ambigua, con mensajes de error similares. El error de Clang es:

prog.cc:16:2: error: call to ''f'' is ambiguous f(7, "ab"); ^ prog.cc:4:27: note: candidate function [with T = int] template<typename T> void f(T, const char*) //#1 ^ prog.cc:9:30: note: candidate function [with N = 3] template<std::size_t N> void f(int, const char(&)[N]) //#2 ^

El IntelliSense de Visual C ++ 2013 (basado en el compilador EDG, hasta donde yo sé) también marca la llamada como ambigua. Curiosamente, el compilador de VC ++ sigue adelante y compila el código sin errores, eligiendo # 2 . (¡Sí! Está de acuerdo conmigo, así que debe ser correcto.)

La pregunta obvia para los expertos es: ¿por qué la llamada es ambigua? ¿Qué me estoy perdiendo (en el área de pedidos parciales, supongo)?


Inicialmente, pensé que el problema con tu código es que no contabas el ajuste del tipo de función. El ajuste del tipo de función hace que una matriz con límites se interprete como un puntero al tipo. Traté de encontrar la solución a su problema preguntando al compilador qué está viendo estáticamente a través de las plantillas, pero en cambio obtengo resultados más interesantes:

#include <iostream> #include <type_traits> template<typename T, std::size_t N> void is_same( const T* _left, const char(&_right)[N] ) { typedef decltype(_left) LeftT; typedef decltype(_right) RightT; std::cout << std::is_same<LeftT,const char*>::value << std::endl; std::cout << std::is_same<LeftT,const char(&)[3]>::value << std::endl; std::cout << std::is_same<LeftT,const char(&)[4]>::value << std::endl; std::cout << std::is_same<RightT,const char*>::value << std::endl; std::cout << std::is_same<RightT,const char(&)[3]>::value << std::endl; std::cout << std::is_same<RightT,const char(&)[4]>::value << std::endl; } int main() { std::cout << std::boolalpha; is_same( "ab", "cd" ); return 0; }

El rendimiento produce: verdadero falso falso falso verdadero falso

El compilador es capaz de distinguir los argumentos en este caso.

Edit 1: Aquí hay un poco más de código. La introducción de referencias rvalue hace que las funciones sean más distinguibles.

#include <iostream> // f template<typename _T> void f( _T, const char* ) { std::cout << "f( _T, const char* )" << std::endl; } template<std::size_t _kN> void f( int, const char(&)[_kN] ) { std::cout << "f( int, const char (&)[_kN] )" << std::endl; } // g template<typename _T> void g( _T, const char* ) { std::cout << "g( _T, const char* )" << std::endl; } template<std::size_t _kN> void g( int, const char(&&)[_kN] ) { std::cout << "g( int, const char (&&)[_kN] )" << std::endl; } // h template<std::size_t _kN> void h( int, const char(&)[_kN] ) { std::cout << "h( int, const char(&)[_kN] )" << std::endl; } template<std::size_t _kN> void h( int, const char(&&)[_kN] ) { std::cout << "h( int, const char (&&)[_kN] )" << std::endl; } int main() { //f( 7, "ab" ); // Error! //f( 7, std::move("ab") ); // Error! f( 7, static_cast<const char*>("ab") ); // OK //f( 7, static_cast<const char(&)[3]>("ab") ); // Error! //f( 7, static_cast<const char(&&)[3]>("ab") ); // Error! g( 7, "ab" ); // OK //g( 7, std::move("ab") ); // Error! g( 7, static_cast<const char*>("ab") ); // OK g( 7, static_cast<const char(&)[3]>("ab") ); // OK //g( 7, static_cast<const char (&&)[3]>("ab") ); // Error! h( 7, "ab" ); // OK (What? Why is this an lvalue?) h( 7, std::move("ab") ); // OK //h( 7, static_cast<const char*>("ab") ); // Error h( 7, static_cast<const char(&)[3]>("ab") ); // OK h( 7, static_cast<const char(&&)[3]>("ab") ); // OK return 0; }