¿Hay garantías para los sindicatos que contienen un tipo envuelto y el tipo en sí?

¿Puedo poner una T y una T envuelta en una union e inspeccionarlas como me gusta?

 union Example { T value; struct Wrapped { T wrapped; } wrapper; }; 
 // for simplicity T = int Example ex; ex.value = 12; cout << ex.wrapper.wrapped; // ? 

Los estándares C ++ 11 solo garantizan la inspección de la secuencia inicial común, pero el value no es una struct . Supongo que la respuesta es no , ya que los tipos envueltos ni siquiera tienen la garantía de que sean compatibles con la memoria de su contraparte no envuelta y el acceso a miembros inactivos solo está bien definido en las secuencias iniciales comunes .

Creo que este es un comportamiento indefinido.

[class.mem] nos da:

La secuencia inicial común de dos tipos de estructuras de disposición estándar es la secuencia más larga de miembros de datos no estáticos y campos de bits en orden de statement, comenzando con la primera entidad de este tipo en cada una de las estructuras, de modo que las entidades correspondientes tengan tipos compatibles con la disposición y ninguna de las dos entidades es un campo de bits o ambas son campos de bits con el mismo ancho. […]

En una unión de diseño estándar con un miembro activo del tipo de estructura T1 , se permite leer un miembro de datos no estáticos m de otro miembro de la unión del tipo de estructura T2 siempre que m forme parte de la secuencia inicial común de T1 y T2 ; el comportamiento es como si el miembro correspondiente de T1 fuera nominado.

Si T no es un tipo de estructura de diseño estándar, este es claramente un comportamiento indefinido. (Tenga en cuenta que int no es un tipo de estructura de diseño estándar, ya que no es un tipo de clase).

Pero incluso para los tipos de estructuras de diseño estándar, lo que constituye una “secuencia inicial común” se basa estrictamente en miembros de datos no estáticos . Es decir, T y struct { T val; } struct { T val; } no tienen una secuencia inicial común – ¡no hay ningún miembro de datos en común en absoluto!

Por lo tanto, aquí:

 template  union Example { T value; struct Wrapped { T wrapped; } wrapper; }; Example ex; ex.value = 12; cout << ex.wrapper.wrapped; // (*) 

estás accediendo a un miembro inactivo del sindicato. Eso está indefinido.

El comportamiento de la unión no está definido cuando se accede a un miembro que no fue el último en escribirse. Así que no, no puedes depender de este comportamiento.

En principio, es idéntico a la idea de tener una unión para extraer bytes específicos de un entero; pero con un riesgo adicional del hecho de que ahora depende del comstackdor y no agrega ningún relleno en su estructura. Consulte Acceso a miembros de la unión inactiva y comportamiento indefinido? para más detalles.

Debería funcionar porque tanto el Example como el Wrapped son clases de diseño estándar , y el estándar C ++ 14 tiene suficientes requisitos para garantizar que, en ese caso, el value y el wrapper.wrapped estén wrapper.wrapped en la misma dirección. El borrador n4296 dice en 9.2 Miembros de la clase [class.mem] §20:

Si un objeto de clase de diseño estándar tiene miembros de datos no estáticos, su dirección es la misma que la de su primer miembro de datos no estáticos.

Una nota incluso dice:

[Nota: Por lo tanto, puede haber un relleno sin nombre dentro de un objeto de estructura de diseño estándar, pero no al principio, según sea necesario para lograr la alineación adecuada. “Nota final”

Eso significa que al menos respeta la regla estricta de aliasing de 3.10 Lvalues ​​y rvalues ​​[basic.lval] §10

Si un progtwig intenta acceder al valor almacenado de un objeto a través de un glvalue distinto de uno de los siguientes tipos, el comportamiento no está definido
– el tipo dynamic del objeto,

– un tipo agregado o de unión que incluye uno de los tipos mencionados anteriormente entre sus elementos o miembros de datos no estáticos (incluido, recursivamente, un elemento o miembro de datos no estáticos de un subagregado o unión contenida),

Así que esto está perfectamente definido:

 cout << *(&ex.wrapper.wrapped) << endl 

porque &ex.wrapper.wrapped debe ser el mismo que &ex.value y el objeto señalado tiene el tipo correcto. . Pero como el estándar es explícito solo para subsecuencias comunes . Así que mi comprensión es cout << ex.wrapper.wrapped << endl invoca un comportamiento indefinido, debido a una nota en 1.3.24 [defns.undefined] sobre el comportamiento indefinido dice (enfatiza el mío):

Puede esperarse un comportamiento indefinido cuando esta Norma Internacional omita cualquier definición explícita de comportamiento ...

TL / DR: apostaría una moneda a que la mayoría de las implementaciones comunes, si no todas, la aceptarían, pero debido a la nota de 1.3.24 [defns.undefined], nunca usaría eso en el código de producción, pero usaría *(&ex.wrapper.wrapped) lugar.


En el borrador más reciente n4659 para C ++ 17, la noción relevante es la inter-convertibilidad ([basic.compound] §4).