c++ - CRTP con argumentos de plantilla de plantilla
templates template-templates (2)
El siguiente código no compila ...
namespace {
template<typename T, template<typename> class D>
struct Base {
Base(const T& _t) : t(_t) { }
T t;
};
template<typename T>
struct Derived : Base<T, Derived> {
Derived(const T& _t) : Base<T, Derived>(_t) { }
};
}
int main(int argc, char* argv[]) {
Derived<int> d(1);
return 0;
}
Hay un error de compilación en la línea - Derived(const T& _t) : Base<T, Derived>(_t) { }
Error C3200 ''`anonymous-namespace'' :: Derived '': argumento de plantilla no válida para el parámetro de plantilla'' D '', se esperaba una plantilla de clase
Esto funciona si proporciono cualquier otra clase que tenga un argumento de plantilla en lugar de Derived.
template<typename T>
struct Other {
};
template<typename T>
struct Derived : Base<T, Other> {
Derived(const T& _t) : Base<T, Other>(_t) { }
};
En una plantilla de clase, el nombre de clase inyectada ( Derived
en su ejemplo) puede ser tanto un nombre de tipo como un nombre de plantilla. El estándar especifica que debe considerarse el nombre de la plantilla cuando se usa como un argumento para un parámetro de plantilla de plantilla (para que su código funcione), pero desafortunadamente algunos compiladores aún no lo han implementado.
Una solución es usar un nombre calificado, de modo que no use el nombre de clase inyectada sino que nombre directamente la plantilla:
template<typename T>
struct Derived : Base<T, Derived> {
Derived(const T& _t) : Base<T, ::Derived>(_t) { }
};
Tl; dr: la forma más portátil y menos extensa de solucionar ese problema parece ser usar el nombre calificado ::Derived
en su ejemplo:
template<typename T>
struct Derived : Base<T, Derived>
{
Derived(const T& _t) : Base<T, ::Derived>(_t) { }
};
¿Por qué?
El problema es que el compilador no cumple con C ++ 11.
Al igual que las clases normales (sin plantilla), las plantillas de clase tienen un nombre de clase inyectado (Cláusula 9). El nombre de clase inyectado se puede utilizar como nombre de plantilla o nombre de tipo. Cuando se usa con una lista de argumentos de plantilla , como un argumento de plantilla para un parámetro de plantilla de plantilla , o como el identificador final en el especificador de tipos elaborados de una declaración de plantilla de clase amiga, se refiere a la plantilla de clase en sí.
Por lo tanto, su código debe compilarse, pero desafortunadamente, todos los compiladores que probé (clang 3.7, Visual Studio 2015 y g ++ 5.3) se niegan a hacerlo.
Afaik, debería poder denotar la plantilla de varias maneras, dentro de la definición Derived
:
- Usando el nombre inyectado directamente
Derived
- Nombre calificado de la plantilla de clase
::Derived
- Usando el nombre de la clase inyectada, designándolo como nombre de plantilla
Derived<T>::template Derived
- Usando un nombre calificado, nuevamente designándolo como nombre de
::template Derived
El estado de compilación de esos compiladores con respecto a esas cuatro opciones es el siguiente (usando el espacio de nombres anonymus; donde +
= compilación exitosa):
+------------------------------+----------+---------+-----------+
| Method | MSVS2015 | g++ 5.3 | clang 3.7 |
+------------------------------+----------+---------+-----------+
| Derived | - | - | - |
| ::Derived | + | + | + |
| Derived<T>::template Derived | - | - | + |
| ::template Derived | + | - | + |
+------------------------------+----------+---------+-----------+
Cuando se le da al espacio de nombres el nombre X
, la imagen cambia un poco (es decir, g++
ahora acepta X::template Derived
mientras que rechazó ::template Derived
):
+---------------------------------+----------+---------+-----------+
| Method | MSVS2015 | g++ 5.3 | clang 3.7 |
+---------------------------------+----------+---------+-----------+
| Derived | - | - | - |
| X::Derived | + | + | + |
| X::Derived<T>::template Derived | - | - | + |
| X::template Derived | + | + | + |
+---------------------------------+----------+---------+-----------+