(Vea aquí también para mi respuesta C ++ 11 )
Para analizar un programa C ++, el compilador necesita saber si ciertos nombres son tipos o no. El siguiente ejemplo demuestra que:
t * f;
¿Cómo se debe analizar esto? Para muchos idiomas, un compilador no necesita saber el significado de un nombre para analizar y básicamente saber qué acción hace una línea de código. Sin embargo, en C ++, lo anterior puede producir interpretaciones muy diferentes según el t
medio. Si es un tipo, entonces será una declaración de un puntero f
. Sin embargo, si no es un tipo, será una multiplicación. Entonces, el Estándar C ++ dice en el párrafo (3/7):
Algunos nombres denotan tipos o plantillas. En general, cada vez que se encuentra un nombre, es necesario determinar si ese nombre denota una de estas entidades antes de continuar analizando el programa que lo contiene. El proceso que determina esto se llama búsqueda de nombres.
¿Cómo descubrirá el compilador a qué se t::x
refiere un nombre si se t
refiere a un parámetro de tipo de plantilla? x
podría ser un miembro de datos int estático que podría multiplicarse o podría ser igualmente una clase anidada o typedef que podría dar lugar a una declaración. Si un nombre tiene esta propiedad, que no se puede buscar hasta que se conozcan los argumentos reales de la plantilla, se llama un nombre dependiente ("depende" de los parámetros de la plantilla).
Puede recomendar esperar hasta que el usuario cree una instancia de la plantilla:
Esperemos hasta que el usuario cree una instancia de la plantilla y luego descubra el significado real de t::x * f;
.
Esto funcionará y, de hecho, el Estándar lo permite como un posible enfoque de implementación. Estos compiladores básicamente copian el texto de la plantilla en un búfer interno, y solo cuando se necesita una instanciación, analizan la plantilla y posiblemente detectan errores en la definición. Pero en lugar de molestar a los usuarios de la plantilla (¡pobres colegas!) Con errores cometidos por el autor de una plantilla, otras implementaciones eligen verificar las plantillas desde el principio y dar errores en la definición lo antes posible, incluso antes de que se produzca una instancia.
Entonces, tiene que haber una manera de decirle al compilador que ciertos nombres son tipos y que ciertos nombres no lo son.
La palabra clave "typename"
La respuesta es: Decidimos cómo el compilador debe analizar esto. Si t::x
es un nombre dependiente, entonces necesitamos ponerle un prefijo typename
para decirle al compilador que lo analice de cierta manera. La Norma dice en (14.6 / 2):
Se supone que un nombre utilizado en una declaración o definición de plantilla y que depende de un parámetro de plantilla no nombra un tipo a menos que la búsqueda de nombre aplicable encuentre un nombre de tipo o el nombre sea calificado por la palabra clave typename.
Hay muchos nombres para los que typename
no es necesario, porque el compilador puede, con la búsqueda de nombre aplicable en la definición de plantilla, descubrir cómo analizar una construcción en sí misma, por ejemplo T *f;
, cuando T
es un parámetro de plantilla de tipo. Pero para t::x * f;
ser una declaración, debe escribirse como typename t::x *f;
. Si omite la palabra clave y se considera que el nombre no es de tipo, pero cuando la instanciación lo encuentra denota un tipo, el compilador emite los mensajes de error habituales. A veces, el error se da en consecuencia en el momento de la definición:
// t::x is taken as non-type, but as an expression the following misses an
// operator between the two names or a semicolon separating them.
t::x f;
La sintaxis typename
solo permite nombres calificados antes ; por lo tanto, se da por sentado que siempre se sabe que los nombres no calificados se refieren a tipos si lo hacen.
Existe un problema similar para los nombres que denotan plantillas, como se insinúa en el texto introductorio.
La palabra clave "plantilla"
¿Recuerda la cita inicial anterior y cómo el Estándar requiere un manejo especial para las plantillas también? Tomemos el siguiente ejemplo de aspecto inocente:
boost::function< int() > f;
Puede parecer obvio para un lector humano. No es así para el compilador. Imagine la siguiente definición arbitraria de boost::function
y f
:
namespace boost { int function = 0; }
int main() {
int f = 0;
boost::function< int() > f;
}
Esa es realmente una expresión válida ! Utiliza el operador menor que para comparar boost::function
contra cero ( int()
), y luego usa el operador mayor que para comparar el resultado bool
contra f
. Sin embargo, como bien sabrá, boost::function
en la vida real es una plantilla, por lo que el compilador sabe (14.2 / 3):
Después de que la búsqueda de nombre (3.4) encuentra que un nombre es un nombre de plantilla, si a este nombre le sigue un <, el <siempre se toma como el comienzo de una lista de argumentos de plantilla y nunca como un nombre seguido por el menor- que el operador
Ahora volvemos al mismo problema que con typename
. ¿Qué sucede si aún no podemos saber si el nombre es una plantilla al analizar el código? Tendremos que insertar template
inmediatamente antes del nombre de la plantilla, según lo especificado por 14.2/4
. Esto se ve así:
t::template f<int>(); // call a function template
Los nombres de plantillas no sólo puede ocurrir después de un ::
sino también después de un ->
o .
en un miembro de la clase de acceso. También debe insertar la palabra clave allí:
this->template f<int>(); // call a function template
Dependencias
Para las personas que tienen libros gruesos de Standardese en su estante y que quieren saber de qué estaba hablando exactamente, hablaré un poco sobre cómo se especifica esto en el Estándar.
En las declaraciones de plantilla, algunas construcciones tienen diferentes significados según los argumentos de plantilla que use para crear una instancia de la plantilla: las expresiones pueden tener diferentes tipos o valores, las variables pueden tener diferentes tipos o las llamadas a funciones pueden terminar llamando a diferentes funciones. Dichas construcciones generalmente se dice que dependen de parámetros de plantilla.
El Estándar define con precisión las reglas según si una construcción es dependiente o no. Los separa en grupos lógicamente diferentes: uno captura tipos, otro captura expresiones. Las expresiones pueden depender de su valor y / o su tipo. Así que tenemos, con ejemplos típicos anexados:
- Tipos dependientes (por ejemplo, un parámetro de plantilla de tipo
T
)
- Expresiones dependientes del valor (por ejemplo, un parámetro de plantilla que no es de tipo
N
)
- Expresiones dependientes del tipo (p. Ej.: Conversión a un parámetro de plantilla de tipo
(T)0
)
La mayoría de las reglas son intuitivas y se crean de forma recursiva: por ejemplo, un tipo construido como T[N]
un tipo dependiente si N
es una expresión dependiente del valor o T
es un tipo dependiente. Los detalles de esto se pueden leer en la sección (14.6.2/1
) para tipos dependientes, (14.6.2.2)
para expresiones dependientes de tipo y (14.6.2.3)
para expresiones dependientes de valor.
Nombres dependientes
El estándar no es claro sobre qué es exactamente un nombre dependiente . En una lectura simple (ya sabes, el principio de menor sorpresa), todo lo que define como un nombre dependiente es el caso especial para los nombres de funciones a continuación. Pero dado que claramente T::x
también debe buscarse en el contexto de creación de instancias, también debe ser un nombre dependiente (afortunadamente, a partir de mediados de C ++ 14 el comité ha comenzado a estudiar cómo solucionar esta definición confusa).
Para evitar este problema, he recurrido a una interpretación simple del texto estándar. De todas las construcciones que denotan tipos o expresiones dependientes, un subconjunto de ellas representa nombres. Esos nombres son, por lo tanto, "nombres dependientes". Un nombre puede tomar diferentes formas: el Estándar dice:
Un nombre es el uso de un identificador (2.11), id-función-operador (13.5), id-función-conversión (12.3.2) o id-plantilla (14.2) que denota una entidad o etiqueta (6.6.4, 6.1)
Un identificador es solo una secuencia simple de caracteres / dígitos, mientras que los dos siguientes son la forma operator +
y operator type
. La última forma es template-name <argument list>
. Todos estos son nombres, y por uso convencional en el Estándar, un nombre también puede incluir calificadores que dicen en qué espacio de nombre o clase se debe buscar un nombre.
Una expresión dependiente del valor 1 + N
no es un nombre, sino que lo N
es. El subconjunto de todas las construcciones dependientes que son nombres se denomina nombre dependiente . Sin embargo, los nombres de las funciones pueden tener un significado diferente en las diferentes instancias de una plantilla, pero desafortunadamente esta regla general no los atrapa.
Nombres de funciones dependientes
No es principalmente una preocupación de este artículo, pero vale la pena mencionarlo: los nombres de las funciones son una excepción que se manejan por separado. El nombre de una función de identificador depende no solo, sino de las expresiones de argumento dependientes del tipo utilizadas en una llamada. En el ejemplo f((T)0)
, f
es un nombre dependiente. En el Estándar, esto se especifica en (14.6.2/1)
.
Notas adicionales y ejemplos
En suficientes casos necesitamos ambos de typename
y template
. Su código debe verse como el siguiente
template <typename T, typename Tail>
struct UnionNode : public Tail {
// ...
template<typename U> struct inUnion {
typedef typename Tail::template inUnion<U> dummy;
};
// ...
};
La palabra clave template
no siempre tiene que aparecer en la última parte de un nombre. Puede aparecer en el medio antes de un nombre de clase que se usa como ámbito, como en el siguiente ejemplo
typename t::template iterator<int>::value_type v;
En algunos casos, las palabras clave están prohibidas, como se detalla a continuación
En el nombre de una clase base dependiente no se le permite escribir typename
. Se supone que el nombre dado es un nombre de tipo de clase. Esto es cierto para ambos nombres en la lista de clase base y la lista de inicializador del constructor:
template <typename T>
struct derive_from_Has_type : /* typename */ SomeBase<T>::type
{ };
En el uso de declaraciones no es posible usar template
después de la última ::
, y el comité de C ++ dijo que no trabajara en una solución.
template <typename T>
struct derive_from_Has_type : SomeBase<T> {
using SomeBase<T>::template type; // error
using typename SomeBase<T>::type; // typename *is* allowed
};