programacion - template typename t c++
El tipo incompleto no está permitido en una clase, pero está permitido en una plantilla de clase (4)
El siguiente es un código no válido:
struct foo {
struct bar;
bar x; // error: field x has incomplete type
struct bar{ int value{42}; };
};
int main() { return foo{}.x.value; }
Esto está bastante claro, ya que foo::bar
se considera incompleto en el punto donde se define foo::x
.
Sin embargo, parece que hay una "solución alternativa" que hace que la misma definición de clase sea válida:
template <typename = void>
struct foo_impl {
struct bar;
bar x; // no problems here
struct bar{ int value{42}; };
};
using foo = foo_impl<>;
int main() { return foo{}.x.value; }
Esto works con todos los compiladores principales. Tengo tres preguntas sobre esto:
- ¿Es este código válido de C ++, o simplemente una peculiaridad de los compiladores?
- Si es un código válido, ¿hay un párrafo en el estándar de C ++ que trate con esta excepción?
- Si es un código válido, ¿por qué la primera versión (sin
template
) se considera inválida? Si el compilador puede descubrir la segunda opción, no veo una razón por la cual no podría encontrar la primera.
Si agrego una especialización explícita para void
:
template <typename = void>
struct foo_impl {};
template<>
struct foo_impl<void> {
struct bar;
bar x; // error: field has incomplete type
struct bar{ int value{42}; };
};
using foo = foo_impl<>;
int main() { return foo{}.x.value; }
De nuevo falla la compilación .
Más detalles sobre la respuesta aceptada.
No estoy seguro de que la respuesta aceptada sea la explicación correcta, pero es la más plausible por el momento. Extrapolando de esa respuesta, aquí están las respuestas a mis preguntas originales:
- ¿Es este código válido de C ++, o simplemente una peculiaridad de los compiladores? [ Es un código válido. ]
- Si es un código válido, ¿hay un párrafo en el estándar de C ++ que trate con esta excepción? [ [temp.point]/4 ]
- Si es un código válido, ¿por qué la primera versión (sin
template
) se considera inválida? Si el compilador puede descubrir la segunda opción, no veo una razón por la cual no podría encontrar la primera. [ Debido a que C ++ es raro , maneja las plantillas de clase de manera diferente a las clases (probablemente podría haber adivinado esta). ]
Algunas explicaciones mas
Lo que parece estar pasando
Al crear una instancia de foo{}
en main
el compilador crea una instancia de una especialización (implícita) para foo_impl<void>
. Esta especialización hace referencia a foo_impl<void>::bar
en la línea 4 ( bar x;
). El contexto está dentro de una definición de plantilla, por lo que depende de un parámetro de plantilla, y la especialización foo_impl<void>::bar
obviamente no está instanciada previamente, por lo que todas las condiciones previas para [temp.point] / 4 se cumplen, y el compilador genera El siguiente código intermedio (pseudo):
template <typename = void>
struct foo_impl {
struct bar;
bar x; // no problems here
struct bar{ int value{42}; };
};
using foo = foo_impl<>;
// implicit specialization of foo_impl<void>::bar, [temp.point]/4
$ struct foo_impl<void>::bar {
$ int value{42};
$ };
// implicit specialization of foo_impl<void>
$ struct foo_impl<void> {
$ struct bar;
$ bar x; // bar is not incomplete here
$ };
int main() { return foo{}.x.value; }
Sobre la especialización
Según [temp.spec]/4 :
Una especialización es una clase, función o miembro de clase que está instanciada o explícitamente especializada.
así que la llamada a foo{}.x.value
en la implementación original con plantillas califica como una especialización (esto era algo nuevo para mí).
Sobre la versión con especialización explícita.
La versión con especialización explícita no compila ya que parece que:
Si el contexto desde el cual se hace referencia a la especialización depende de un parámetro de plantilla
ya no se cumple, por lo que la regla de [temp.point] / 4 no se aplica.
Contestaré la tercera parte de su pregunta, como IANALL (no un abogado de idiomas).
El código no es válido por la misma razón por la que no es válido usar una función antes de que se haya declarado, incluso aunque el compilador pueda averiguar qué se supone que es la función al ir más abajo en la misma unidad de traducción. Y los casos son similares también en el sentido de que si tiene solo una declaración sin definición, eso es suficiente para el compilador, mientras que aquí tiene una definición de plantilla antes de la creación de instancias.
Entonces, el punto es: el estándar de idioma obliga a que el compilador no lo busque cuando quiere definir algo (y una plantilla de clase no es una definición de una clase).
Creo que este ejemplo está permitido explícitamente por
17.6.1.2 Clases de miembro de plantillas de clase [temp.mem.class]
1 Una clase miembro de una plantilla de clase puede definirse fuera de la definición de plantilla de clase en la que se declara. [Nota: la clase miembro debe definirse antes de su primer uso que requiere una instanciación (17.8.1) Por ejemplo,
template<class T> struct A { class B; }; A<int>::B* b1; // OK: requires A to be defined but not A::B template<class T> class A<T>::B { }; A<int>::B b2; // OK: requires A::B to be defined
"Nota final"
Esto debería work bien también:
template <typename = void>
struct foo_impl {
struct bar;
bar x; // no problems here
};
template<typename T>
struct foo_impl<T>::bar{ int value{42}; };
using foo = foo_impl<>;
int main()
{
return foo{}.x.value;
}
La respuesta real podría ser ¯ / _ (ツ) _ / ¯, pero probablemente esté bien en este momento porque las plantillas son mágicas, pero puede ser más explícitamente no estar bien en espera de otras resoluciones de problemas centrales.
Primero, el principal problema, por supuesto, es [class.mem]/14 :
Los miembros de datos no estáticos no tendrán tipos incompletos.
Esta es la razón por la que su ejemplo sin plantilla está mal formado. Sin embargo, de acuerdo con [temp.point]/4 :
Para una especialización de plantilla de clase, una especialización de plantilla de miembro de clase o una especialización para un miembro de clase de una plantilla de clase, si la especialización se crea implícitamente porque está referenciada dentro de otra especialización de plantilla, si el contexto desde el que se hace referencia a la especialización depende en un parámetro de plantilla, y si la especialización no está instanciada antes de la creación de instancias de la plantilla adjunta, el punto de creación de instancias es inmediatamente anterior al punto de creación de instancias de la plantilla adjunta . De lo contrario, el punto de instanciación para tal especialización precede inmediatamente a la declaración o definición del alcance del espacio de nombres que se refiere a la especialización.
Lo que sugiere que foo_impl<void>::bar
se foo_impl<void>
instancia antes que foo_impl<void>
, y por lo tanto, se completa en el punto donde se foo_impl<void>
instancia del miembro de datos no estáticos de la bar
de tipo. Así que tal vez está bien.
Sin embargo , los problemas de lenguaje central 1626 y 2335 tratan con problemas no exactamente iguales pero todavía bastante similares en cuanto a la integridad y las plantillas, y ambos apuntan a querer hacer que el caso de la plantilla sea más consistente con el caso que no es de plantilla.
¿Qué significa todo esto cuando se ve como un todo? No estoy seguro.