enum c++11 gcc c++14 language-lawyer clang++

c++11 - typedef enum



usando vs. typedef-¿hay alguna diferencia sutil, menos conocida? (2)

Esto me parece un error en GCC.

Tenga en cuenta que [decl.typedef] no dice que una declaración de alias es una declaración typedef

Tienes razón, [dcl.dcl] p9 da una definición del término typedef declaración que excluye alias-declaraciones s. Sin embargo, [dcl.typedef] dice explícitamente, como citó en su pregunta:

2 Un typedef-name también puede ser introducido por una declaración de alias . El identificador que sigue a la palabra clave using convierte en un typedef-name y el atributo opcional -especificador-seq que sigue al identificador pertenece a ese typedef-name . Tiene la misma semántica que si fuera introducida por el especificador typedef . [...]

"La misma semántica" no deja ninguna duda. Bajo la interpretación de GCC, typedef y using tienen diferentes semánticas, por lo tanto, la única conclusión razonable es que la interpretación de GCC es incorrecta. Cualquier regla que se aplique a las declaraciones typedef debe interpretarse como aplicada a las declaraciones de alias también.

Fondo

Todos estan de acuerdo en que

using <typedef-name> = <type>;

es equivalente a

typedef <type> <typedef-name>;

y que lo primero es preferible a lo segundo por varias razones (vea Scott Meyers, Effective Modern C ++ y varias preguntas relacionadas sobre el flujo de apilamiento).

Esto está respaldado por [dcl.typedef]:

Un typedef-name también puede ser introducido por una declaración de alias. El identificador que sigue a la palabra clave using se convierte en un typedef-name y el atributo opcional-especificador-seq que sigue al identificador pertenece a ese typedef-name. Dicho typedef-name tiene la misma semántica que si fuera introducido por el especificador typedef.

Sin embargo, considere una declaración como

typedef struct { int val; } A;

Para este caso, [dcl.typedef] especifica:

Si la declaración typedef define una clase sin nombre (o enumeración), el primer nombre de typedef declarado por la declaración como ese tipo de clase (o tipo enum) se usa para denotar el tipo de clase (o tipo enum) solo para fines de vinculación (3.5 ).

La sección de referencia 3.5 [basic.link] dice

Un nombre que tenga un ámbito de espacio de nombres al que no se le ha dado un enlace interno anterior tiene el mismo enlace que el espacio de nombres adjunto si es el nombre de una clase sin nombre definida [...] en una declaración typedef en la que la clase tiene el nombre typedef para el enlace fines [...]

Suponiendo que la declaración typedef anterior se realice en el espacio de nombres global, la estructura A tendría un enlace externo, ya que el espacio de nombres global tiene un enlace externo.

Pregunta

La pregunta ahora es si lo mismo es cierto, si la declaración typedef se reemplaza por una declaración de alias de acuerdo con la noción común de que son equivalentes:

using A = struct { int val; };

En particular, ¿el tipo A declarado a través de la declaración de alias ("utilizando") tiene el mismo enlace que el declarado a través de la declaración typedef?

Tenga en cuenta que [decl.typedef] no dice que una declaración de alias es una declaración typedef (solo dice que ambos introducen un nombre typedef) y que [decl.typedef] habla solo de una declaración typedef (no una declaración de alias) con la propiedad de introducir un nombre typedef para fines de vinculación . Si la declaración de alias no es capaz de introducir un nombre typedef para fines de vinculación, A solo sería un alias para un tipo anónimo y no tendrá vinculación alguna.

OMI, esa es al menos una posible interpretación, aunque estricta, de la norma. Por supuesto, puede que esté pasando por alto algo.

Esto plantea las siguientes preguntas:

  • Si de hecho existe esta diferencia sutil, ¿es por intención o es un descuido en la norma?
  • ¿Cuál es el comportamiento esperado de los compiladores / enlazadores?

Investigación

El siguiente programa mínimo que consta de tres archivos (necesitamos al menos dos unidades de compilación separadas) se utiliza para investigar el problema.

a.hpp

#ifndef A_HPP #define A_HPP #include <iosfwd> #if USING_VS_TYPEDEF using A = struct { int val; }; #else typedef struct { int val; } A; #endif void print(std::ostream& os, A const& a); #endif // A_HPP

a.cpp

#include "a.hpp" #include <iostream> void print(std::ostream& os, A const& a) { os << a.val << "/n"; }

main.cpp

#include "a.hpp" #include <iostream> int main() { A a; a.val = 42; print(std::cout, a); }

GCC

Compilar esto con gcc 7.2 con la variante "typedef" compila limpiamente y proporciona el resultado esperado:

> g++ -Wall -Wextra -pedantic-errors -DUSING_VS_TYPEDEF=0 a.cpp main.cpp > ./a.out 42

La compilación con la variante "utilizando" produce un error de compilación:

> g++ -Wall -Wextra -pedantic-errors -DUSING_VS_TYPEDEF=1 a.cpp main.cpp a.cpp:4:6: warning: ‘void print(std::ostream&, const A&)’ defined but not used [-Wunused-function] void print(std::ostream& os, A const& a) ^~~~~ In file included from main.cpp:1:0: a.hpp:16:6: error: ‘void print(std::ostream&, const A&)’, declared using unnamed type, is used but never defined [-fpermissive] void print(std::ostream& os, A const& a); ^~~~~ a.hpp:9:2: note: ‘using A = struct<unnamed>’ does not refer to the unqualified type, so it is not used for linkage }; ^ a.hpp:16:6: error: ‘void print(std::ostream&, const A&)’ used but never defined void print(std::ostream& os, A const& a); ^~~~~

Parece que GCC sigue la interpretación estricta del estándar anterior y marca una diferencia en lo que respecta al vínculo entre typedef y la declaración de alias.

Sonido metálico

Usando el Clang 6, ambas variantes compilan y ejecutan limpiamente sin ninguna advertencia:

> clang++ -Wall -Wextra -pedantic-errors -DUSING_VS_TYPEDEF=0 a.cpp main.cpp > ./a.out 42 > clang++ -Wall -Wextra -pedantic-errors -DUSING_VS_TYPEDEF=1 a.cpp main.cpp > ./a.out 42

Por lo tanto, también se podría preguntar

  • ¿Qué compilador es correcto?

Parece que el estándar no está claro en esto.

Por un lado,

[dcl.typedef] Un typedef-name también puede ser introducido por una declaración de alias . [...] Tal typedef-name tiene la misma semántica como si hubiera sido introducido por el especificador typedef .

Por otro lado, el estándar separa claramente las nociones de declaración typedef y declaración de alias (el último término es un nombre de producción gramatical, por lo que está en cursiva y con guión; el primero no lo es). En algunos contextos se habla de "una declaración typedef o una declaración de alias ", haciéndolos equivalentes en estos contextos; ya veces se trata únicamente de "una declaración typedef". En particular, cuando el estándar habla de vinculación y declaraciones typedef, solo habla de declaraciones typedef y no menciona la declaración de alias . Esto incluye el pasaje clave.

[dcl.typedef] Si la declaración typedef define una clase sin nombre (o enumeración), el primer nombre de typedef declarado por la declaración como ese tipo de clase (o tipo enum) se usa para denotar el tipo de clase (o tipo enum) para sólo con fines de vinculación.

Tenga en cuenta que el estándar insiste en el primer typedef-name que se utiliza para el enlace. Esto significa que en

typedef struct { int x; } A, B;

solo A se usa para vinculación, y B no se usa. Nada en la norma indica que un nombre introducido por alias-declaración debería comportarse como A y no como B

En mi opinión, el estándar no es suficientemente claro en esta área. Si la intención es hacer que solo la declaración typedef funcione para el enlace, sería apropiado declarar explícitamente en [dcl.typedef] que la declaración de alias no lo hace. Si la intención es hacer que la declaración de alias funcione para el enlace, esto también debería indicarse explícitamente, como se hace en otros contextos.