todas - tipos de funciones en c++
¿En qué punto se produce la vinculación de instancias de plantilla? (1)
Este código es del "lenguaje de programación C ++" de Bjarne Stroustrup (C.13.8.3 Punto de enlace de instanciación)
template <class T>
void f(T value)
{
g(value);
}
void g(int v);
void h()
{
extern g(double);
f(2);
}
Y menciona:
Aquí, el punto de creación de instancias para f () es justo antes de h (), por lo que g () llamado en f () es la g (int) global en lugar de la g (doble) local. La definición de "punto de instanciación" implica que un parámetro de plantilla nunca puede vincularse a un nombre local o un miembro de clase.
void h()
{
struct X {}; // local structure
std::vector<X> v; // error: can''t use local structure as template parameter
}
Mis preguntas son:
¿Por qué debería funcionar el primer código?
g()
se declara más tarde, y realmente obtengo un error con G ++ 4.9.2 queg
no se declara en ese momento.extern g (doble) - ¿Cómo funciona esto? ¿Ya que el valor de retorno no importa en caso de una sobrecarga de funciones, podemos perderlo en las declaraciones a futuro?
el punto de instanciación para f () es justo antes de h () : ¿por qué? ¿no es lógico que se ejecute cuando se llame a
f(2)
? Justo donde lo llamamos, desde dondeg(double)
ya estará dentro del alcance.La definición de "punto de instanciación" implica que un parámetro de plantilla nunca puede vincularse a un nombre local o a un miembro de la clase . ¿Ha cambiado esto en C ++ 14? Recibo un error con C ++ (G ++ 4.9.2), pero no obtengo un error con C ++ 14 (G ++ 4.9.2).
"En 1985, se lanzó la primera edición de The C ++ Programming Language, que se convirtió en la referencia definitiva para el idioma, ya que aún no existía un estándar oficial ". wiki Así que no cambió entre C ++ 11 y C ++ 14. Puedo asumir (y por favor tome esto con un grano de sal) que cambió entre "pre-estandarización" y estandarización. Tal vez alguien que conozca mejor la historia de C ++ pueda arrojar más luz aquí.
En cuanto a lo que realmente sucede:
Primero salgamos de la manera simple:
extern g(double);
Esto no es válido en C ++. Históricamente, desafortunadamente C permitía omisiones de tipo. En C ++ tienes que escribir extern void g(double)
.
A continuación, ignoremos la sobrecarga de g(double)
para responder tu primera pregunta:
template <class T>
void f(T value)
{
g(value);
}
void g(int v);
int main()
{
f(2);
}
En C ++ hay la búsqueda de nombre de dos fases infame:
- En la primera fase, en la definición de la plantilla, se resuelven todos los nombres no dependientes . El no hacerlo es un error duro;
- Los nombres de los dependientes se resuelven en la fase dos, en la instanciación de la plantilla.
Las reglas son un poco más complicadas, pero eso es lo esencial.
g
depende del parámetro de plantilla T
por lo que pasa la primera fase. Eso significa que si nunca crea una instancia de f
, el código se compila bien. En la segunda fase f
se crea una instancia con T = int
. g(int)
ahora se busca, pero no se encuentra:
17 : error: call to function ''g'' that is neither visible in the template definition nor found by argument-dependent lookup g(value); ^ 24 : note: in instantiation of function template specialization ''f<int>'' requested here f(2); ^ 20 : note: ''g'' should be declared prior to the call site void g(int v);
Para que un nombre arbitrario g
pase con éxito, tenemos algunas opciones:
- Declarar
g
anteriormente:
void g(int);
template <class T>
void f(T value)
{
g(value);
}
- trae
g
conT
:
template <class T>
void f(T)
{
T::g();
}
struct X {
static void g();
};
int main()
{
X x;
f(x);
}
- Traiga
g
conT
través de ADL:
template <class T>
void f(T value)
{
g(value);
}
struct X {};
void g(X);
int main()
{
X x;
f(x);
}
Estos por supuesto cambian la semántica del programa. Están destinados a ilustrar lo que puede y no puede tener en una plantilla.
En cuanto a por qué ADL no encuentra g(int)
, pero encuentra g(X)
:
§ 3.4.2 Búsqueda de nombre dependiente del argumento [basic.lookup.argdep]
Para cada tipo de argumento T en la llamada de función, hay un conjunto de cero o más espacios de nombres asociados y un conjunto de cero o más clases asociadas que deben considerarse [...]:
Si T es un tipo fundamental, sus conjuntos de espacios de nombres y clases asociados están vacíos.
Si T es un tipo de clase (incluidas las uniones), sus clases asociadas son: la clase en sí misma; la clase de la que es miembro, en su caso; y sus clases básicas directas e indirectas. Sus espacios de nombres asociados son los espacios de nombres de los cuales sus clases asociadas son miembros. [...]
Y finalmente llegamos a por qué extern void g(double);
no se encuentra dentro de main: en primer lugar mostramos que g(fundamental_type)
se encuentra iff se declara antes de la definición de f
. Así que vamos a dejarlo void g(X)
dentro de la main
. ¿ADL lo encuentra?
template <class T>
void f(T value)
{
g(value);
}
struct X{};
int main()
{
X x;
void g(X);
f(x);
}
No. Debido a que no reside en el mismo espacio de nombres que X
(es decir, espacio de nombres global), ADL no puede encontrarlo.
Prueba de que g
no está en global
int main()
{
void g(X);
X x;
g(x); // OK
::g(x); // ERROR
}
34: error: ningún miembro llamado ''g'' en el espacio de nombres global; ¿Querías decir simplemente ''g''?