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
T1can 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
vof 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 T2are object types and where the alignment requirements ofT2are 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_castshould suffice in the case of pointers, as do [expr.static.cast]/13 and [conv.ptr]/2; and - [conversions to] references to
voidare 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.