Can I put a T
and a wrapped T
in an union
and inspect them as I like?
union Example {
T value;
struct Wrapped {
T wrapped;
} wrapper;
};
// for simplicity T = int
Example ex;
ex.value = 12;
cout << ex.wrapper.wrapped; // ?
The C++11 standards only guarantee save inspection of the common initial sequence, but value
isn't a struct
. I guess the answer is no, since wrapped types aren't even guaranteed to be memory compatible to their unwrapped counterpart and accessing inactive members is only well-defined on common initial sequences.
I believe this is undefined behavior.
[class.mem] gives us:
The common initial sequence of two standard-layout struct types is the longest sequence of non-static data members and bit-fields in declaration order, starting with the first such entity in each of the structs, such that corresponding entities have layout-compatible types and either neither entity is a bit-field or both are bit-fields with the same width. [...]
In a standard-layout union with an active member of struct type T1
, it is permitted to read a non-static data member m
of another union member of struct type T2
provided m is part of the common initial sequence of T1
and T2
; the behavior is as if the corresponding member of T1
were nominated.
If T
isn't a standard layout struct type, this is clearly undefined behavior. (Note that int
is not a standard layout struct type, as it's not a class type at all).
But even for standard layout struct types, what constitutes a "common initial sequence" is based strictly on non-static data members. That is, T
and struct { T val; }
do not have a common initial sequence - there are no data members in common at all!
Hence, here:
template <typename T>
union Example {
T value;
struct Wrapped {
T wrapped;
} wrapper;
};
Example<int> ex;
ex.value = 12;
cout << ex.wrapper.wrapped; // (*)
you're accessing an inactive member of the union. That's undefined.
Union behavior is undefined when accessing a member that wasn't the last one written to. So no, you can't depend on this behavior.
It's identical in principle to the idea of having a union to extract specific bytes from an integer; but with additional risk of the fact that you're now depending on the compiler not adding any padding in your struct. See Accessing inactive union member and undefined behavior? for more details.
It should work because both Example
and Wrapped
are standard layout classes, and C++14 standard has enough requirements to guarantee that in that case value
and wrapper.wrapped
are located at the same address. Draft n4296 says in 9.2 Class members [class.mem] §20:
If a standard-layout class object has any non-static data members, its address is the same as the address
of its first non-static data member.
A note even says:
[ Note: There might therefore be unnamed padding within a standard-layout struct
object, but not at its beginning, as necessary to achieve appropriate alignment. —end note ]
That means that you at least respect the strict aliasing rule from 3.10 Lvalues and rvalues [basic.lval] §10
If a program attempts to access the stored value of an object through a glvalue of other than one of the
following types the behavior is undefined
— the dynamic type of the object,
...
— an aggregate or union type that includes one of the aforementioned types among its elements or nonstatic
data members (including, recursively, an element or non-static data member of a subaggregate
or contained union),
So this is perfectly defined:
cout << *(&ex.wrapper.wrapped) << endl
because &ex.wrapper.wrapped
is required to be the same as &ex.value
and the pointed object has the correct type.
.
But as the standard is explicit only for common subsequence. So my understanding is cout << ex.wrapper.wrapped << endl
invokes undefined behaviour, because of a note in 1.3.24 [defns.undefined] about
undefined behavior
says (emphasize mine):
Undefined behavior may be expected when this International Standard omits any explicit definition of
behavior...
TL/DR: I would bet a coin that most if not all common implementation will accept it, but because of the note from 1.3.24 [defns.undefined], I would never use that in production code but would use *(&ex.wrapper.wrapped)
instead.
In the more recent draft n4659 for C++17, the relevant notion is inter-convertibility ([basic.compound] §4).