universo sangre matematicas historia curiosamente ciencia adn c++ templates language-lawyer template-meta-programming

c++ - sangre - Curiosamente las definiciones de clase mutuamente recurrentes



curiosamente matematicas (1)

Quiero que las declaraciones de tipo en dos clases dependan mutuamente entre sí. Aquí hay un primer ejemplo que compila con clang y gcc:

template <class Sum> struct A { using X = char; // (1) using Z = typename Sum::B::Y; // (2) }; template <class Sum> struct B { using Y = typename Sum::A::X; }; struct AplusB { using A = ::A<AplusB>; using B = ::B<AplusB>; }; AplusB::A::Z z; int main() {}

Sin embargo, hay un momento interesante. Si intercambias las líneas (1) y (2), no se compilará con un error:

error: ningún tipo llamado ''X'' en ''A''

¿Eso me hace cuestionar si el código original es realmente válido en el sentido del estándar C ++, o simplemente sucede que se compila?

Aquí hay un segundo ejemplo, que también explota el orden de creación de instancias de la plantilla:

template <class Sum> struct A { using X = char; using P = typename Sum::B::Q; }; template <class Sum> struct B { using Y = typename Sum::A::X; using Q = int; }; struct AplusB { using A = ::A<AplusB>; using B = ::B<AplusB>; }; AplusB::A::X z; // (1) AplusB::B::Q t; // (2) int main() {}

Aquí si cambias (1) y (2) no se compilará con el error:

error: ningún tipo llamado ''Q'' en ''B''

Entonces la pregunta es: ¿Está realmente permitido por el estándar que las definiciones de clase dependan unas de otras de esa manera?


Como se discutió en otra answer , la resolución de CWG 287 es solo el enfoque de facto seguido por las implementaciones, que obliga a que las entidades que preceden al PoI "en línea" del miembro que está siendo instanciado estén dentro del alcance.

Por lo tanto, cuando se intercambian las líneas, intentamos acceder a algo que aún no se ha instanciado:

template <class Sum> struct A { using X = char; // (#) using P = typename Sum::B::Q; // (2.3), (1.2) }; template <class Sum> struct B { using Y = typename Sum::A::X; // (2.2), (1.3) using Q = int; // (*) }; struct AplusB { using A = ::A<AplusB>; // (1.1) using B = ::B<AplusB>; // (2.1) }; AplusB::B::Q t; // (2) AplusB::A::X z; // (1) (INDEPENDENT example)

La secuencia (2), (2.1), (2.2) y (2.3) es el orden en que se producen las instancias, con el PoI efectivo que precede a esa declaración. Con (2) declarado primero, en (2.3), hacemos referencia a (*), que no está dentro del alcance. Si (1) es lo primero, luego en (1.3) accedemos a (#), lo que de hecho está dentro del alcance. (En una declaración posterior de (2), la especialización de A ya se ha instanciado completamente, por lo que no hay más sutilezas).

Si declara todos los alias de "caso base" antes de los "recursivos", que fue la diferencia en su primer fragmento, funciona bien de cualquier manera .