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).