c++ templates default-arguments

c++ - ¿Por qué el compilador no puede deducir el tipo de plantilla de los argumentos predeterminados?



templates default-arguments (3)

En C ++ 03, la especificación prohíbe explícitamente que el argumento predeterminado se use para deducir un argumento de plantilla (C ++ 03 §14.8.2 / 17):

Un parámetro de tipo de plantilla no se puede deducir del tipo de argumento predeterminado de una función.

En C ++ 11, puede proporcionar un argumento de plantilla predeterminado para la plantilla de función:

template <typename T = float> void bar(int a, T b = 0.0f) { }

Sin embargo, el argumento de la plantilla por defecto es obligatorio. Si no se proporciona el argumento de la plantilla predeterminada, el argumento de la función predeterminada aún no se puede utilizar para la deducción del argumento de la plantilla. Específicamente, se aplica lo siguiente (C ++ 11 14.8.2.5/5):

Los contextos no deducidos son:

...

  • Un parámetro de plantilla utilizado en el tipo de parámetro de un parámetro de función que tiene un argumento predeterminado que se está utilizando en la llamada para la cual se realiza la deducción de argumentos.

Me sorprendió que el siguiente código diera como resultado could not deduce template argument for T error could not deduce template argument for T :

struct foo { template <typename T> void bar(int a, T b = 0.0f) { } }; int main() { foo a; a.bar(5); return 0; }

Llamar a una a.bar<float>(5) soluciona el problema. ¿Por qué el compilador no puede deducir el tipo del argumento predeterminado?


Habría algunas dificultades técnicas para lograr eso en general. Recuerde que los argumentos predeterminados en las plantillas no se crean instancias hasta que son necesarios. Considera entonces:

template<typename T, typename U> void f(U p = T::g()); // (A) template<typename T> T f(long, int = T()); // (B) int r = f<int>(1);

Esto se resuelve hoy al realizar (entre otras cosas) los siguientes pasos:

  1. intento de deducir parámetros de plantilla para los candidatos (A) y (B); esto falla para (A), que por lo tanto se elimina.
  2. realizar resolución de sobrecarga; (B) está seleccionado
  3. forma la llamada, instanciando el argumento por defecto

Para deducir de un argumento por defecto, ese argumento por defecto tendría que crearse una instancia antes de completar el proceso de deducción. Eso podría fallar, dando lugar a errores fuera del contexto SFINAE. Es decir, un candidato que puede ser completamente inapropiado para una llamada podría provocar un error.


Una buena razón podría ser que

void foo(bar, xyzzy = 0);

Es similar a un par de sobrecargas.

void foo(bar b) { foo(b, 0); } foo(bar, xyzzy);

Además, a veces es ventajoso refactorizarlo en tal:

void foo(bar b) { /* something other than foo(b, 0); */ } foo(bar, xyzzy);

Incluso cuando se escribe como una, sigue siendo como dos funciones en una, ninguna de las cuales es "preferida" en ningún sentido. Estás llamando a la función de un argumento; El argumento de dos es efectivamente una función diferente. La notación de argumento por defecto simplemente los combina en uno.

Si la sobrecarga tuviera el comportamiento que está solicitando, entonces, para mantener la coherencia, tendría que funcionar en el caso cuando la plantilla se divide en dos definiciones. ¡Eso no tendría sentido porque entonces la deducción sería sacar tipos de una función no relacionada que no se está llamando! Y si no se implementó, significaría que la sobrecarga de diferentes longitudes de lista de parámetros se convierte en un "ciudadano de segunda clase" en comparación con la "argumentación por defecto".

Es bueno si la diferencia entre las sobrecargas y la configuración predeterminada está completamente oculta para el cliente.