It appears to be widely-held that type punning via reinterpret_cast
is somehow prohibited (properly: "undefined behavior", that is, "behavior for which this International Standard imposes no requirements", with an explicit note that implementations may define behavior) in C++. Am I incorrect in using the following reasoning to disagree, and if so, why?
[expr.reinterpret.cast]/11 states:
A glvalue expression of type
T1
can be cast to the type “reference toT2
” if an expression of type “pointer toT1
” can be explicitly converted to the type “pointer toT2
” using areinterpret_cast
. The result refers to the same object as the source glvalue, but with the specified type. [ Note: That is, for lvalues, a reference castreinterpret_cast<T&>(x)
has the same effect as the conversion*reinterpret_cast<T*>(&x)
with the built-in&
and*
operators (and similarly forreinterpret_cast<T&&>(x)
). — end note ] No temporary is created, no copy is made, and constructors or conversion functions are not called.
with the footnote:
75) This is sometimes referred to as a type pun.
/11 implicitly, via example, carries the restrictions of /6 through /10, but perhaps the most common usage (punning objects) is addressed in [expr.reinterpret.cast]/7:
An object pointer can be explicitly converted to an object pointer of a different type. When a prvalue
v
of object pointer type is converted to the object pointer type “pointer tocv T
”, the result isstatic_cast<cv T*>(static_cast<cv void*>(v))
. [ Note: Converting a prvalue of type “pointer toT1
” to the type “pointer toT2
” (whereT1 and T2
are object types and where the alignment requirements ofT2
are no stricter than those ofT1
) and back to its original type yields the original pointer value. — end note ]
Clearly the purpose cannot be conversion to/from pointers or references to void
, as:
- the example in /7 clearly demonstrates that
static_cast
should suffice in the case of pointers, as do [expr.static.cast]/13 and [conv.ptr]/2; and - [conversions to] references to
void
are prima facie invalid.
Further, [basic.lval]/8 states:
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:
(8.1) the dynamic type of the object,
(8.2) a cv-qualified version of the dynamic type of the object,
(8.3) a type similar to the dynamic type of the object,
(8.4) a type that is the signed or unsigned type corresponding to the dynamic type of the object,
(8.5) a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
(8.6) an aggregate or union type that includes one of the aforementioned types among its elements or non-static data members (including, recursively, an element or non-static data member of a subaggregate or contained union),
(8.7) a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
(8.8) a char, unsigned char, or std::byte type.
And if we return to [expr.reinterpret.cast]/11 for a moment, we see "The result refers to the same object as the source glvalue, but with the specified type." This reads to me as an explicit statement that the result of reinterpret_cast<T&>(v)
is an lvalue reference to an object of type T
, to which access is clearly "through a glvalue of" "the dynamic type of the object". This sentence also addresses the argument that various paragraphs of [basic.life] apply via the spurious claim that the results of such conversions refer to a new object of type T
, the lifetime of which has not yet begun, which just happens to reside at the same memory address as v
.
It seems nonsensical to explicitly define such conversions only to disallow standard-defined use of the results, particularly in light of footnote 75 noting that such [reference] conversion is "sometimes referred to as a type pun."
Note that my references are to the final publicly-available draft for C++17 (N4659), but the language in question is little-changed from N3337 (C++11) through N4788 (C++20 WD) (tip link will likely refer to later drafts in time). In fact the footnote to [expr.reinterpret.cast]/11 is made even more explicit in the most recent draft:
This is sometimes referred to as a type pun when the result refers to the same object as the source glvalue.