2012-03-07 18 views
7

¿Cómo exactamente una declaración de plantilla coincide con una definición de plantilla? Encontré un texto en el estándar sobre plantilla-ids que hace referencia a la misma función si "sus nombres de plantilla [...] se refieren a la misma plantilla y [...]" (14.4 [temp.temporal] p1) pero no puedo encontrar una definición para los nombres de plantilla o cuando nombres de plantilla se refieren a la misma plantilla. No estoy seguro si estoy en el camino correcto porque no he descifrado la gramática lo suficiente como para decir si un plantilla-id es parte de la definición/declaración de una plantilla, o simplemente el uso de una modelo.¿Cómo se relacionan las definiciones de plantilla con las declaraciones de plantilla?

Por ejemplo, el siguiente programa funciona bien.

#include <iostream> 

template<typename T> 
T foo(T t); 

int main() { 
    foo(1); 
} 

template<typename T> 
T foo(T t) 
{ std::cout << "A\n"; return 0; } 

Si cambio la forma en que uso los parámetros de plantilla en la definición de plantilla los nombres al parecer ya no se refieren a la misma plantilla, y la falla de enlace.

#include <iostream> 

template<typename T> 
T foo(T t); 

int main() { 
    foo(1); 
} 

template<typename T> 
int foo(T t) { std::cout << "A\n"; return 0; } 

// or 

template<typename T> 
struct identity { 
    typedef T type; 
}; 

template<typename T> 
typename identity<T>::type 
foo(T t) { std::cout << "A\n"; return 0; } 

A continuación, si muevo la definición de plantilla a otra unidad de traducción, para mi aplicación de C++ (MSVC 11 beta) El programa funciona sin importar lo que digo los tipos.

//main.cpp 

template<typename T> 
T foo(T t); 

int main() { 
    foo(1); 
} 

//definition.cpp 
#include <iostream> 

template<typename T> 
struct identity { 
    typedef T type; 
}; 

template<typename T> 
typename identity<T>::type 
foo(T t) { std::cout << "A\n"; return 0; } 

template int foo<int>(int); 

o

//definition.cpp 
#include <iostream> 

template<typename T> 
int foo(T t) { std::cout << "A\n"; return 0; } 

template int foo<int>(int); 

o incluso si la definición no es una plantilla en absoluto:

//definition.cpp 
#include <iostream> 

int foo(T t) { std::cout << "A\n"; return 0; } 

Obviamente vinculación está teniendo éxito porque la firma/nombre revuelto es el mismo, independientemente de la plantilla que fue instanciada para crear el símbolo. Creo que este comportamiento no definido porque yo estoy violando:

§ 14.1 [temp] P6

Una plantilla de función, función miembro de una plantilla de clase, o miembro de datos estática de una plantilla de clase deberá se definirá en cada unidad de traducción en la que esté instanciado implícitamente (14.7.1) a menos que la especialización correspondiente esté explícitamente instanciada (14.7.2) en alguna unidad de traducción; no se requiere diagnóstico.

Pero luego dicen que trato de cumplir con esos requisitos, poniendo una definición de la plantilla en la segunda unidad de traducción, y que incluye una instanciación explícita en uno de dos lugares:

#include <iostream> 

template<typename T> 
T foo(T t) { std::cout << "A\n"; return 0; } 

// Location 1  

template<typename T> 
int foo(int t) { std::cout << "B\n"; return 0; } 

// Location 2 

¿Cuáles son las reglas sobre la desambiguación de a qué plantilla se refiere una instanciación explícita? Poniéndolo en la Ubicación 1 hace que se cree una instancia de la plantilla correcta y esa definición se use en el programa final, mientras que ponerlo en la Ubicación 2 ejemplifica la otra plantilla y causa lo que creo que es un comportamiento indefinido bajo 14.1 p6 arriba.

Por otro lado una instancia implícita de dos definiciones plantillas recoge la primera plantilla no importa qué, así que parece que la regla para eliminar la ambigüedad de las plantillas es diferente en estas circunstancias:

#include <iostream> 

template<typename T> 
T foo(T t) { std::cout << "A\n"; return 0; } 

template<typename T> 
int foo(int t) { std::cout << "B\n"; return 0; } 

int main() { 
    foo(1); // prints "A" 
} 

La razón de este vino arriba está relacionada con this question donde el interrogador descubrió que una sola declaración adelantada

template<typename T> 
T CastScriptVarConst(const ScriptVar_t& s); 

no podría actuar como una declaración de múltiples definiciones de plantilla:

template<typename T> 
typename std::enable_if<GetType<T>::value < SVT_BASEOBJ,T>::type 
CastScriptVarConst(const ScriptVar_t& s) { 
    return (T) s; 
} 

template<typename T> 
typename std::enable_if<!(GetType<T>::value < SVT_BASEOBJ) 
         && std::is_base_of<CustomVar,T>::value,T>::type 
CastScriptVarConst(const ScriptVar_t& s) { 
    return *s.as<T>(); 
} 

Y quería entender mejor la relación entre las definiciones de las plantillas y las declaraciones.

+0

Busque una copia de "C++ Templates: The Complete Guide" para la historia en toda regla. Y sí, es más digerible que el estándar ... mucho en realidad;) – 0xC0000022L

Respuesta

4

Bien, comencemos desde el principio. El "nombre-plantilla" de una plantilla es el nombre real de la función o clase que se está modelando; es decir, en

template<class T> T foo(T t); 

foo es el nombre de la plantilla. Para las plantillas de funciones, la regla para decidir si son iguales es bastante larga, como se describe en 14.5.5.1 "Sobrecarga de plantillas de funciones". El párrafo 6 de esa sección (estoy citando de C++ 03 aquí, por lo que la redacción y los números de párrafos pueden haber cambiado en C++ 11) define los términos equivalente y funcionalmente equivalentes, cuando se aplica a expresiones que involucran plantilla parámetros.

En resumen, equivalentes expresiones son los mismos, aparte de la posibilidad de tener diferentes nombres para los parámetros de plantilla, y funcionalmente equivalentes expresiones son los mismos si llegan a evaluar a la misma cosa. Por ejemplo, los dos f primeras declaraciones son equivalente, pero el tercero es solamente funcionalmente equivalente a los otros dos: -

template<int A, int B> 
void f(array<A + B>); 
template<int T1, int T2> 
void f(array<T1 + T2>); 
template<int A, int B> 
void f(array< mpl::plus< mpl::int<A>, mpl::int<B> >::value >); 

Continúa en el párrafo 7 para extender esos dos definiciones a plantillas de función enteros . Dos plantillas de función que coinciden (en nombre, ámbito y lista de parámetros de plantilla) son equivalentes si también tienen tipos de devolución y tipos de argumentos equivalentes, o funcionalmente equivalentes si solo tienen tipos de devolución y tipos de argumentos funcionalmente equivalentes. En cuanto a su segundo ejemplo, estas dos funciones sólo son funcionalmente equivalentes: -

template<typename T> 
T foo(T t); 

template<typename T> 
typename identity<T>::type foo(T t); 

párrafo 7 se cierra con la seria advertencia de que, "Si un programa contiene declaraciones de plantillas de funciones que son funcionalmente equivalentes, pero no es equivalente, el programa está mal formado, no se requiere diagnóstico ". Su segundo ejemplo es, por lo tanto, no válido C++. Detectar errores como ese requeriría que cada declaración y definición de una plantilla de función se anotara en el binario con un AST que describa la expresión de plantilla de la que provienen cada parámetro y el tipo de retorno, por lo que el estándar no requiere implementaciones para detectarlo. MSVC está justificado al compilar su tercer ejemplo de la forma en que lo intentó, pero estaría justificado que se rompa.

Pasando a la instanciación explícita, la sección importante es 14.7, "Creación de instancias y especialización de plantilla".El párrafo 5 no permite todo lo siguiente:

  • Ejemplificación explícita de una plantilla más de una vez;
  • Instancia explícita y especialización explícita de la misma plantilla;
  • Especialización explícita de una plantilla para el mismo conjunto de argumentos más de una vez.

Nuevamente, "no se requiere diagnóstico" ya que es bastante difícil de detectar.

lo que para ampliar su ejemplo explícita de instancias, el código siguiente rompe la segunda regla y es ilegal: -

/* Template definition. */ 
template<typename T> 
T foo(T t) 
{ ... } 

/* Specialization, OK in itself. */ 
template< > 
int foo(int t) 
{ ... } 

/* Explicit instantiation, OK in itself. */ 
template< > 
int foo(int t); 

Esto es ilegal, independientemente de la ubicación de la especialización explícita y la creación de instancias explícita, pero por supuesto Debido a que no se requiere diagnóstico, puede obtener resultados útiles en algunos compiladores. Tenga en cuenta también la diferencia entre instanciación explícita y especialización explícita. está mal formado el siguiente ejemplo porque declara una especialización explícita sin definirlo: - está bien formada-

template<typename T> 
T f(T f) 
{ ... } 

template< > 
int f(int); 

void g(void) 
{ f(3); } 

pero este ejemplo, porque tiene una instanciación explícita: -

template<typename T> 
T f(T f) 
{ ... } 

template f(int); 

void g(void) 
{ f(3); } 

la < > hace la diferencia Tenga en cuenta también que, incluso cuando defina una especialización explícita, tiene que ser antes de, de lo contrario el compilador podría haber generado una instanciación implícita para esa plantilla. Esto está en 14.7.3 "Especialización explícita", párrafo 6, justo debajo de donde estaba leyendo, y nuevamente, no se requiere diagnóstico. Para adaptar el mismo ejemplo, esto está mal formada: -

template<typename T> 
T f(T f) 
{ ... } 

void g(void) 
{ f(3); } // Implicitly specializes int f(int) 

template< > 
int f(int) // Too late for an explicit specialization 
{ ... } 

Si no estaba confundido lo suficiente, sin embargo, echar un vistazo a su último ejemplo: -

template<typename T> 
T foo(T t) { ... } 

template<typename T> 
int foo(int t) { ... } 

La segunda definición de foo es no una especialización de la primera definición. Tendría que ser template< > int foo(int) para ser una especialización de template<typename T> T foo(T). Pero eso está bien: se permite la sobrecarga de funciones, y está permitido entre plantillas de funciones y funciones normales. Las llamadas del formulario foo(3) siempre usarán la primera definición, porque su parámetro de plantilla T se puede deducir del tipo de argumento. La segunda definición no permite que su parámetro de plantilla se deduzca del tipo de argumento. Sólo mediante la especificación explícita T puede llegar a la segunda definición, y sólo entonces, cuando la llamada no es ambigua con la primera definición: -

f<int>(3); // ambiguous 
f<string>(3); // can only be the second one 

Todo el proceso de hacer la resolución de sobrecarga para plantillas de función es demasiado largo para describir aquí . Lea la sección 14.8.3 si está interesado y haga más preguntas :-)

Cuestiones relacionadas