Is reinterpret_cast type punning actually undefined behavior?
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.
c++ casting language-lawyer reinterpret-cast type-punning
|
show 6 more comments
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.
c++ casting language-lawyer reinterpret-cast type-punning
4
I haven't noticed that belief being held even narrowly.
– molbdnilo
Jan 1 at 13:19
1
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". No, you're wrong here. I don't understand why you think this. If you access the result, you access it through the typeT
, not the dynamic type ofv
.
– geza
Jan 1 at 14:19
1
A glvalue does't have a dynamic type. A glvalue is a value and values just have a type. The only kind of entity that has a dynamic type is an object. A value is not an object.
– Michael Kenzel
Jan 1 at 14:35
1
No object of typeT
exists (reinterpret_cast does not create an object)
– M.M
Jan 3 at 7:35
4
@MichaelKenzel No; dynamic type is a property that an expression has (it's not a property of an object). An object just has its type. An expression may have different dynamic type to its static type , in the case that the expression designates a base class subobject
– M.M
Jan 3 at 7:38
|
show 6 more comments
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.
c++ casting language-lawyer reinterpret-cast type-punning
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.
c++ casting language-lawyer reinterpret-cast type-punning
c++ casting language-lawyer reinterpret-cast type-punning
asked Jan 1 at 13:02
pandorafalterspandorafalters
1146
1146
4
I haven't noticed that belief being held even narrowly.
– molbdnilo
Jan 1 at 13:19
1
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". No, you're wrong here. I don't understand why you think this. If you access the result, you access it through the typeT
, not the dynamic type ofv
.
– geza
Jan 1 at 14:19
1
A glvalue does't have a dynamic type. A glvalue is a value and values just have a type. The only kind of entity that has a dynamic type is an object. A value is not an object.
– Michael Kenzel
Jan 1 at 14:35
1
No object of typeT
exists (reinterpret_cast does not create an object)
– M.M
Jan 3 at 7:35
4
@MichaelKenzel No; dynamic type is a property that an expression has (it's not a property of an object). An object just has its type. An expression may have different dynamic type to its static type , in the case that the expression designates a base class subobject
– M.M
Jan 3 at 7:38
|
show 6 more comments
4
I haven't noticed that belief being held even narrowly.
– molbdnilo
Jan 1 at 13:19
1
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". No, you're wrong here. I don't understand why you think this. If you access the result, you access it through the typeT
, not the dynamic type ofv
.
– geza
Jan 1 at 14:19
1
A glvalue does't have a dynamic type. A glvalue is a value and values just have a type. The only kind of entity that has a dynamic type is an object. A value is not an object.
– Michael Kenzel
Jan 1 at 14:35
1
No object of typeT
exists (reinterpret_cast does not create an object)
– M.M
Jan 3 at 7:35
4
@MichaelKenzel No; dynamic type is a property that an expression has (it's not a property of an object). An object just has its type. An expression may have different dynamic type to its static type , in the case that the expression designates a base class subobject
– M.M
Jan 3 at 7:38
4
4
I haven't noticed that belief being held even narrowly.
– molbdnilo
Jan 1 at 13:19
I haven't noticed that belief being held even narrowly.
– molbdnilo
Jan 1 at 13:19
1
1
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". No, you're wrong here. I don't understand why you think this. If you access the result, you access it through the type
T
, not the dynamic type of v
.– geza
Jan 1 at 14:19
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". No, you're wrong here. I don't understand why you think this. If you access the result, you access it through the type
T
, not the dynamic type of v
.– geza
Jan 1 at 14:19
1
1
A glvalue does't have a dynamic type. A glvalue is a value and values just have a type. The only kind of entity that has a dynamic type is an object. A value is not an object.
– Michael Kenzel
Jan 1 at 14:35
A glvalue does't have a dynamic type. A glvalue is a value and values just have a type. The only kind of entity that has a dynamic type is an object. A value is not an object.
– Michael Kenzel
Jan 1 at 14:35
1
1
No object of type
T
exists (reinterpret_cast does not create an object)– M.M
Jan 3 at 7:35
No object of type
T
exists (reinterpret_cast does not create an object)– M.M
Jan 3 at 7:35
4
4
@MichaelKenzel No; dynamic type is a property that an expression has (it's not a property of an object). An object just has its type. An expression may have different dynamic type to its static type , in the case that the expression designates a base class subobject
– M.M
Jan 3 at 7:38
@MichaelKenzel No; dynamic type is a property that an expression has (it's not a property of an object). An object just has its type. An expression may have different dynamic type to its static type , in the case that the expression designates a base class subobject
– M.M
Jan 3 at 7:38
|
show 6 more comments
3 Answers
3
active
oldest
votes
I believe your misunderstanding lies here:
This reads to me as an explicit statement that the result of
reinterpret_cast<T&>(v)
is an lvalue reference to an object of typeT
, to which access is clearly "through a glvalue of" "the dynamic type of the object".
[basic.lval]/8 is a bit misleading because it talks about the dynamic type "of the object" when the dynamic type is actually a property of the glvalue [defns.dynamic.type] used to access the object rather than the object itself. Essentially, the dynamic type of the glvalue is the type of the object that is currently living in the place that the glvalue refers to (effectively, the type of the object that was constructed/initialized in that piece of memory) [intro.object]/6. For example:
float my_float = 42.0f;
std::uint32_t& ui = reinterpret_cast<std::uint32_t&>(my_float);
here, ui
is a reference that refers to the object created by the definition of my_float
. Accessing this object through the glvalue ui
would invoke undefined behavior (per [basic.lval]/8.1), however, because the dynamic type of the glvalue is float
while the type of the glvalue is std::uint32_t
.
There are few valid uses of a reinterpret_cast
like that, but use cases other than just casting to void*
and back exist (for the latter, static_cast
would be sufficient, as you noted yourself). [basic.lval]/8 effectively gives you a complete list of what they are. For example, it would be valid to examine (and even copy if the dynamic type of the object is trivially-copyable [basic.types]/9) the value of an object by casting the address of the object to char*
, unsigned char*
, or std::byte8
(not signed char*
, however). It would be valid to reinterpret_cast
an object of signed type to access it as its corresponding unsigned type and vice versa. It would also be valid to cast a pointer/reference to a union to a pointer/reference to a member of that union and access that member through the resulting lvalue if that member is the active member of the union…
The main reason why type punning through casts like this is undefined in general is that making it defined behavior would prohibit some extremely vital compiler optimizations. If you'd allow any object of any type to simply be accessed through an lvalue of any other type, then the compiler would have to assume that any modification of an object through some lvalue can potentially affect the value of any object in the program unless it can prove otherwise. As a result, it would basically be impossible, for example, to keep stuff around in registers for any useful period of time because any modification of anything would immediately invalidate whatever you may have in registers at the moment. Yes, any good optimizer will perform aliasing analysis. But, while such methods certainly work and are powerful, they can, out of principle, only cover a subset of cases. Disproving or proving aliasing in general is basically impossible (equivalent to solving the halting problem I would think)…
2
Areinterpret_cast
does not return an object. It's an expression that evaluates to a value which either points or refers to an object. The standard doesn't say anything about the type of the object being changed. It simply states that a glvalue is returned that has a different type but refers to the same object. We're not talking about sign conversions here, we're talking about interpreting the value of an object as another type, which is fundamentally different from a conversion. Reference conversion can be used in some of the cases listed in [basic.lval]/8 as described above.
– Michael Kenzel
Jan 1 at 14:18
3
Again, there are some things that can only be achieved viareinterpret_cast
. For example, how would you cast a pointer to an integer and back withoutreinterpret_cast
? How would you cast a function pointer to a different function pointer type and back? Just because there are cases wherereinterpret_cast
is effectively just a shorthand or just one of several ways to get the same result doesn't mean that there's no justification for it to exist at all. Just because not every possible use of feature X is also a valid use of that feature doesn't mean there are no valid uses of feature X…
– Michael Kenzel
Jan 1 at 15:09
1
"The lifetime of an object of type T begins when: (1.1) storage with the proper alignment and size for type T is obtained" So there is an object there.
– curiousguy
Jan 2 at 16:22
2
@curiousguy sorry, I have no idea what you're talking about. There can only be one complete object alive in any piece of memory at any given moment. As soon as another object is created in some piece of storage, the lifetime of whatever object may currently reside in there ends [basic.life]/1.4. One of the defining characteristics of the very concept of an object is that an object has an identity. In the C++ object model, that means that no two complete objects can have the same address [intro.object]/9.
– Michael Kenzel
Jan 3 at 1:36
4
I don't understand how a philosophical discussion on the potential merits or lack thereof of the C++ object model is relevant to the question or my answer here. The quote above concerns the lifetime of an object. Before you can talk about the lifetime of an object, you first need an object to talk about. [basic.memobj]/1 clearly specifies what language constructs give rise to objects. Just because the C++ standard is not absolutely flawless doesn't mean that nothing can be said about anything and any interpretation is equally valid…
– Michael Kenzel
Jan 3 at 2:26
|
show 19 more comments
[basic.lval]/8 says when the behavior will surely be undefined, but this does not necessarily mean that if you do something from the list in [basic.lval]/8 the behavior will be defined.
[basic.lval]/8 hasn't been changed much since C++98 and it has inaccurate wording such as the use of an undefined term "dynamic type of the object". (C++ defines dynamic types for expressions).
Behavior definedness, in case you do something allowed by [basic.lval]/8, depends on other parts of the standard. Even if it might be agreed that the result of signed/unsigned reinterpretation could be derived from wording in [basic.types], I can't imagine how is it possible to predict the result of an access to an object containing references or virtual methods through char glvalue.
C++17's new pointer and glvalue casting rules made [basic.lval]/8 more useless, because now it is not formally possible to achieve the aims [basic.lval]/8 intended to guarantee (for example, to read the bytes in an object through char
glvalue). As you pointed out, per [expr.reinterpret.cast]/7, after reinterpret_cast to a reference to T, the resulting glvalue still refers to the object the argument of reinterpret_cast referred to.
Per [conv.lval]/(3.4), the result of the lvalue-to-rvalue conversion is the value contained in the object to which the converted glvalue refers. For example, these rules mean that the result of the lvalue-to-rvalue conversion applied to reinterpret_cast<char&>(i)
, where i
is an int
variable, is the value stored in the i
int
object. The type of the prvalue is char
([conv.lval]/1) and if the value of i
is not representable by char
, according to [expr]/1 the behavior is undefined. Trying to read an int
object through char
glvalue will result in UB if the value of the object is not representable by char
, even though this access is "allowed" by [basic.lval]/(8.8). This proves what have been said in the first paragraph.
C++ specification at the fundamental level looks like a trainwreck
– curiousguy
Jan 3 at 20:21
@curiousguy fixed the answer. Actually, you don't get the value of anint
object if it is not representable bychar
type, this would be UB.
– Language Lawyer
Jan 7 at 6:09
add a comment |
A reference built using reinterpret_cast
(I include casting a pointer and then dereferencing) can be roundtripped to the original type, if the intermediate types had equal or less stringent alignment requirements.
Most other uses are undefined behavior, due to the strict aliasing rule. (no language citation needed because the question already quotes it)
Notable legal cases where the expression final type does not match the object's dynamic type include aliasing through narrow character types, and the common initial sequence rule for structures.
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53995657%2fis-reinterpret-cast-type-punning-actually-undefined-behavior%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
I believe your misunderstanding lies here:
This reads to me as an explicit statement that the result of
reinterpret_cast<T&>(v)
is an lvalue reference to an object of typeT
, to which access is clearly "through a glvalue of" "the dynamic type of the object".
[basic.lval]/8 is a bit misleading because it talks about the dynamic type "of the object" when the dynamic type is actually a property of the glvalue [defns.dynamic.type] used to access the object rather than the object itself. Essentially, the dynamic type of the glvalue is the type of the object that is currently living in the place that the glvalue refers to (effectively, the type of the object that was constructed/initialized in that piece of memory) [intro.object]/6. For example:
float my_float = 42.0f;
std::uint32_t& ui = reinterpret_cast<std::uint32_t&>(my_float);
here, ui
is a reference that refers to the object created by the definition of my_float
. Accessing this object through the glvalue ui
would invoke undefined behavior (per [basic.lval]/8.1), however, because the dynamic type of the glvalue is float
while the type of the glvalue is std::uint32_t
.
There are few valid uses of a reinterpret_cast
like that, but use cases other than just casting to void*
and back exist (for the latter, static_cast
would be sufficient, as you noted yourself). [basic.lval]/8 effectively gives you a complete list of what they are. For example, it would be valid to examine (and even copy if the dynamic type of the object is trivially-copyable [basic.types]/9) the value of an object by casting the address of the object to char*
, unsigned char*
, or std::byte8
(not signed char*
, however). It would be valid to reinterpret_cast
an object of signed type to access it as its corresponding unsigned type and vice versa. It would also be valid to cast a pointer/reference to a union to a pointer/reference to a member of that union and access that member through the resulting lvalue if that member is the active member of the union…
The main reason why type punning through casts like this is undefined in general is that making it defined behavior would prohibit some extremely vital compiler optimizations. If you'd allow any object of any type to simply be accessed through an lvalue of any other type, then the compiler would have to assume that any modification of an object through some lvalue can potentially affect the value of any object in the program unless it can prove otherwise. As a result, it would basically be impossible, for example, to keep stuff around in registers for any useful period of time because any modification of anything would immediately invalidate whatever you may have in registers at the moment. Yes, any good optimizer will perform aliasing analysis. But, while such methods certainly work and are powerful, they can, out of principle, only cover a subset of cases. Disproving or proving aliasing in general is basically impossible (equivalent to solving the halting problem I would think)…
2
Areinterpret_cast
does not return an object. It's an expression that evaluates to a value which either points or refers to an object. The standard doesn't say anything about the type of the object being changed. It simply states that a glvalue is returned that has a different type but refers to the same object. We're not talking about sign conversions here, we're talking about interpreting the value of an object as another type, which is fundamentally different from a conversion. Reference conversion can be used in some of the cases listed in [basic.lval]/8 as described above.
– Michael Kenzel
Jan 1 at 14:18
3
Again, there are some things that can only be achieved viareinterpret_cast
. For example, how would you cast a pointer to an integer and back withoutreinterpret_cast
? How would you cast a function pointer to a different function pointer type and back? Just because there are cases wherereinterpret_cast
is effectively just a shorthand or just one of several ways to get the same result doesn't mean that there's no justification for it to exist at all. Just because not every possible use of feature X is also a valid use of that feature doesn't mean there are no valid uses of feature X…
– Michael Kenzel
Jan 1 at 15:09
1
"The lifetime of an object of type T begins when: (1.1) storage with the proper alignment and size for type T is obtained" So there is an object there.
– curiousguy
Jan 2 at 16:22
2
@curiousguy sorry, I have no idea what you're talking about. There can only be one complete object alive in any piece of memory at any given moment. As soon as another object is created in some piece of storage, the lifetime of whatever object may currently reside in there ends [basic.life]/1.4. One of the defining characteristics of the very concept of an object is that an object has an identity. In the C++ object model, that means that no two complete objects can have the same address [intro.object]/9.
– Michael Kenzel
Jan 3 at 1:36
4
I don't understand how a philosophical discussion on the potential merits or lack thereof of the C++ object model is relevant to the question or my answer here. The quote above concerns the lifetime of an object. Before you can talk about the lifetime of an object, you first need an object to talk about. [basic.memobj]/1 clearly specifies what language constructs give rise to objects. Just because the C++ standard is not absolutely flawless doesn't mean that nothing can be said about anything and any interpretation is equally valid…
– Michael Kenzel
Jan 3 at 2:26
|
show 19 more comments
I believe your misunderstanding lies here:
This reads to me as an explicit statement that the result of
reinterpret_cast<T&>(v)
is an lvalue reference to an object of typeT
, to which access is clearly "through a glvalue of" "the dynamic type of the object".
[basic.lval]/8 is a bit misleading because it talks about the dynamic type "of the object" when the dynamic type is actually a property of the glvalue [defns.dynamic.type] used to access the object rather than the object itself. Essentially, the dynamic type of the glvalue is the type of the object that is currently living in the place that the glvalue refers to (effectively, the type of the object that was constructed/initialized in that piece of memory) [intro.object]/6. For example:
float my_float = 42.0f;
std::uint32_t& ui = reinterpret_cast<std::uint32_t&>(my_float);
here, ui
is a reference that refers to the object created by the definition of my_float
. Accessing this object through the glvalue ui
would invoke undefined behavior (per [basic.lval]/8.1), however, because the dynamic type of the glvalue is float
while the type of the glvalue is std::uint32_t
.
There are few valid uses of a reinterpret_cast
like that, but use cases other than just casting to void*
and back exist (for the latter, static_cast
would be sufficient, as you noted yourself). [basic.lval]/8 effectively gives you a complete list of what they are. For example, it would be valid to examine (and even copy if the dynamic type of the object is trivially-copyable [basic.types]/9) the value of an object by casting the address of the object to char*
, unsigned char*
, or std::byte8
(not signed char*
, however). It would be valid to reinterpret_cast
an object of signed type to access it as its corresponding unsigned type and vice versa. It would also be valid to cast a pointer/reference to a union to a pointer/reference to a member of that union and access that member through the resulting lvalue if that member is the active member of the union…
The main reason why type punning through casts like this is undefined in general is that making it defined behavior would prohibit some extremely vital compiler optimizations. If you'd allow any object of any type to simply be accessed through an lvalue of any other type, then the compiler would have to assume that any modification of an object through some lvalue can potentially affect the value of any object in the program unless it can prove otherwise. As a result, it would basically be impossible, for example, to keep stuff around in registers for any useful period of time because any modification of anything would immediately invalidate whatever you may have in registers at the moment. Yes, any good optimizer will perform aliasing analysis. But, while such methods certainly work and are powerful, they can, out of principle, only cover a subset of cases. Disproving or proving aliasing in general is basically impossible (equivalent to solving the halting problem I would think)…
2
Areinterpret_cast
does not return an object. It's an expression that evaluates to a value which either points or refers to an object. The standard doesn't say anything about the type of the object being changed. It simply states that a glvalue is returned that has a different type but refers to the same object. We're not talking about sign conversions here, we're talking about interpreting the value of an object as another type, which is fundamentally different from a conversion. Reference conversion can be used in some of the cases listed in [basic.lval]/8 as described above.
– Michael Kenzel
Jan 1 at 14:18
3
Again, there are some things that can only be achieved viareinterpret_cast
. For example, how would you cast a pointer to an integer and back withoutreinterpret_cast
? How would you cast a function pointer to a different function pointer type and back? Just because there are cases wherereinterpret_cast
is effectively just a shorthand or just one of several ways to get the same result doesn't mean that there's no justification for it to exist at all. Just because not every possible use of feature X is also a valid use of that feature doesn't mean there are no valid uses of feature X…
– Michael Kenzel
Jan 1 at 15:09
1
"The lifetime of an object of type T begins when: (1.1) storage with the proper alignment and size for type T is obtained" So there is an object there.
– curiousguy
Jan 2 at 16:22
2
@curiousguy sorry, I have no idea what you're talking about. There can only be one complete object alive in any piece of memory at any given moment. As soon as another object is created in some piece of storage, the lifetime of whatever object may currently reside in there ends [basic.life]/1.4. One of the defining characteristics of the very concept of an object is that an object has an identity. In the C++ object model, that means that no two complete objects can have the same address [intro.object]/9.
– Michael Kenzel
Jan 3 at 1:36
4
I don't understand how a philosophical discussion on the potential merits or lack thereof of the C++ object model is relevant to the question or my answer here. The quote above concerns the lifetime of an object. Before you can talk about the lifetime of an object, you first need an object to talk about. [basic.memobj]/1 clearly specifies what language constructs give rise to objects. Just because the C++ standard is not absolutely flawless doesn't mean that nothing can be said about anything and any interpretation is equally valid…
– Michael Kenzel
Jan 3 at 2:26
|
show 19 more comments
I believe your misunderstanding lies here:
This reads to me as an explicit statement that the result of
reinterpret_cast<T&>(v)
is an lvalue reference to an object of typeT
, to which access is clearly "through a glvalue of" "the dynamic type of the object".
[basic.lval]/8 is a bit misleading because it talks about the dynamic type "of the object" when the dynamic type is actually a property of the glvalue [defns.dynamic.type] used to access the object rather than the object itself. Essentially, the dynamic type of the glvalue is the type of the object that is currently living in the place that the glvalue refers to (effectively, the type of the object that was constructed/initialized in that piece of memory) [intro.object]/6. For example:
float my_float = 42.0f;
std::uint32_t& ui = reinterpret_cast<std::uint32_t&>(my_float);
here, ui
is a reference that refers to the object created by the definition of my_float
. Accessing this object through the glvalue ui
would invoke undefined behavior (per [basic.lval]/8.1), however, because the dynamic type of the glvalue is float
while the type of the glvalue is std::uint32_t
.
There are few valid uses of a reinterpret_cast
like that, but use cases other than just casting to void*
and back exist (for the latter, static_cast
would be sufficient, as you noted yourself). [basic.lval]/8 effectively gives you a complete list of what they are. For example, it would be valid to examine (and even copy if the dynamic type of the object is trivially-copyable [basic.types]/9) the value of an object by casting the address of the object to char*
, unsigned char*
, or std::byte8
(not signed char*
, however). It would be valid to reinterpret_cast
an object of signed type to access it as its corresponding unsigned type and vice versa. It would also be valid to cast a pointer/reference to a union to a pointer/reference to a member of that union and access that member through the resulting lvalue if that member is the active member of the union…
The main reason why type punning through casts like this is undefined in general is that making it defined behavior would prohibit some extremely vital compiler optimizations. If you'd allow any object of any type to simply be accessed through an lvalue of any other type, then the compiler would have to assume that any modification of an object through some lvalue can potentially affect the value of any object in the program unless it can prove otherwise. As a result, it would basically be impossible, for example, to keep stuff around in registers for any useful period of time because any modification of anything would immediately invalidate whatever you may have in registers at the moment. Yes, any good optimizer will perform aliasing analysis. But, while such methods certainly work and are powerful, they can, out of principle, only cover a subset of cases. Disproving or proving aliasing in general is basically impossible (equivalent to solving the halting problem I would think)…
I believe your misunderstanding lies here:
This reads to me as an explicit statement that the result of
reinterpret_cast<T&>(v)
is an lvalue reference to an object of typeT
, to which access is clearly "through a glvalue of" "the dynamic type of the object".
[basic.lval]/8 is a bit misleading because it talks about the dynamic type "of the object" when the dynamic type is actually a property of the glvalue [defns.dynamic.type] used to access the object rather than the object itself. Essentially, the dynamic type of the glvalue is the type of the object that is currently living in the place that the glvalue refers to (effectively, the type of the object that was constructed/initialized in that piece of memory) [intro.object]/6. For example:
float my_float = 42.0f;
std::uint32_t& ui = reinterpret_cast<std::uint32_t&>(my_float);
here, ui
is a reference that refers to the object created by the definition of my_float
. Accessing this object through the glvalue ui
would invoke undefined behavior (per [basic.lval]/8.1), however, because the dynamic type of the glvalue is float
while the type of the glvalue is std::uint32_t
.
There are few valid uses of a reinterpret_cast
like that, but use cases other than just casting to void*
and back exist (for the latter, static_cast
would be sufficient, as you noted yourself). [basic.lval]/8 effectively gives you a complete list of what they are. For example, it would be valid to examine (and even copy if the dynamic type of the object is trivially-copyable [basic.types]/9) the value of an object by casting the address of the object to char*
, unsigned char*
, or std::byte8
(not signed char*
, however). It would be valid to reinterpret_cast
an object of signed type to access it as its corresponding unsigned type and vice versa. It would also be valid to cast a pointer/reference to a union to a pointer/reference to a member of that union and access that member through the resulting lvalue if that member is the active member of the union…
The main reason why type punning through casts like this is undefined in general is that making it defined behavior would prohibit some extremely vital compiler optimizations. If you'd allow any object of any type to simply be accessed through an lvalue of any other type, then the compiler would have to assume that any modification of an object through some lvalue can potentially affect the value of any object in the program unless it can prove otherwise. As a result, it would basically be impossible, for example, to keep stuff around in registers for any useful period of time because any modification of anything would immediately invalidate whatever you may have in registers at the moment. Yes, any good optimizer will perform aliasing analysis. But, while such methods certainly work and are powerful, they can, out of principle, only cover a subset of cases. Disproving or proving aliasing in general is basically impossible (equivalent to solving the halting problem I would think)…
edited Jan 4 at 20:22
answered Jan 1 at 13:19


Michael KenzelMichael Kenzel
4,4181920
4,4181920
2
Areinterpret_cast
does not return an object. It's an expression that evaluates to a value which either points or refers to an object. The standard doesn't say anything about the type of the object being changed. It simply states that a glvalue is returned that has a different type but refers to the same object. We're not talking about sign conversions here, we're talking about interpreting the value of an object as another type, which is fundamentally different from a conversion. Reference conversion can be used in some of the cases listed in [basic.lval]/8 as described above.
– Michael Kenzel
Jan 1 at 14:18
3
Again, there are some things that can only be achieved viareinterpret_cast
. For example, how would you cast a pointer to an integer and back withoutreinterpret_cast
? How would you cast a function pointer to a different function pointer type and back? Just because there are cases wherereinterpret_cast
is effectively just a shorthand or just one of several ways to get the same result doesn't mean that there's no justification for it to exist at all. Just because not every possible use of feature X is also a valid use of that feature doesn't mean there are no valid uses of feature X…
– Michael Kenzel
Jan 1 at 15:09
1
"The lifetime of an object of type T begins when: (1.1) storage with the proper alignment and size for type T is obtained" So there is an object there.
– curiousguy
Jan 2 at 16:22
2
@curiousguy sorry, I have no idea what you're talking about. There can only be one complete object alive in any piece of memory at any given moment. As soon as another object is created in some piece of storage, the lifetime of whatever object may currently reside in there ends [basic.life]/1.4. One of the defining characteristics of the very concept of an object is that an object has an identity. In the C++ object model, that means that no two complete objects can have the same address [intro.object]/9.
– Michael Kenzel
Jan 3 at 1:36
4
I don't understand how a philosophical discussion on the potential merits or lack thereof of the C++ object model is relevant to the question or my answer here. The quote above concerns the lifetime of an object. Before you can talk about the lifetime of an object, you first need an object to talk about. [basic.memobj]/1 clearly specifies what language constructs give rise to objects. Just because the C++ standard is not absolutely flawless doesn't mean that nothing can be said about anything and any interpretation is equally valid…
– Michael Kenzel
Jan 3 at 2:26
|
show 19 more comments
2
Areinterpret_cast
does not return an object. It's an expression that evaluates to a value which either points or refers to an object. The standard doesn't say anything about the type of the object being changed. It simply states that a glvalue is returned that has a different type but refers to the same object. We're not talking about sign conversions here, we're talking about interpreting the value of an object as another type, which is fundamentally different from a conversion. Reference conversion can be used in some of the cases listed in [basic.lval]/8 as described above.
– Michael Kenzel
Jan 1 at 14:18
3
Again, there are some things that can only be achieved viareinterpret_cast
. For example, how would you cast a pointer to an integer and back withoutreinterpret_cast
? How would you cast a function pointer to a different function pointer type and back? Just because there are cases wherereinterpret_cast
is effectively just a shorthand or just one of several ways to get the same result doesn't mean that there's no justification for it to exist at all. Just because not every possible use of feature X is also a valid use of that feature doesn't mean there are no valid uses of feature X…
– Michael Kenzel
Jan 1 at 15:09
1
"The lifetime of an object of type T begins when: (1.1) storage with the proper alignment and size for type T is obtained" So there is an object there.
– curiousguy
Jan 2 at 16:22
2
@curiousguy sorry, I have no idea what you're talking about. There can only be one complete object alive in any piece of memory at any given moment. As soon as another object is created in some piece of storage, the lifetime of whatever object may currently reside in there ends [basic.life]/1.4. One of the defining characteristics of the very concept of an object is that an object has an identity. In the C++ object model, that means that no two complete objects can have the same address [intro.object]/9.
– Michael Kenzel
Jan 3 at 1:36
4
I don't understand how a philosophical discussion on the potential merits or lack thereof of the C++ object model is relevant to the question or my answer here. The quote above concerns the lifetime of an object. Before you can talk about the lifetime of an object, you first need an object to talk about. [basic.memobj]/1 clearly specifies what language constructs give rise to objects. Just because the C++ standard is not absolutely flawless doesn't mean that nothing can be said about anything and any interpretation is equally valid…
– Michael Kenzel
Jan 3 at 2:26
2
2
A
reinterpret_cast
does not return an object. It's an expression that evaluates to a value which either points or refers to an object. The standard doesn't say anything about the type of the object being changed. It simply states that a glvalue is returned that has a different type but refers to the same object. We're not talking about sign conversions here, we're talking about interpreting the value of an object as another type, which is fundamentally different from a conversion. Reference conversion can be used in some of the cases listed in [basic.lval]/8 as described above.– Michael Kenzel
Jan 1 at 14:18
A
reinterpret_cast
does not return an object. It's an expression that evaluates to a value which either points or refers to an object. The standard doesn't say anything about the type of the object being changed. It simply states that a glvalue is returned that has a different type but refers to the same object. We're not talking about sign conversions here, we're talking about interpreting the value of an object as another type, which is fundamentally different from a conversion. Reference conversion can be used in some of the cases listed in [basic.lval]/8 as described above.– Michael Kenzel
Jan 1 at 14:18
3
3
Again, there are some things that can only be achieved via
reinterpret_cast
. For example, how would you cast a pointer to an integer and back without reinterpret_cast
? How would you cast a function pointer to a different function pointer type and back? Just because there are cases where reinterpret_cast
is effectively just a shorthand or just one of several ways to get the same result doesn't mean that there's no justification for it to exist at all. Just because not every possible use of feature X is also a valid use of that feature doesn't mean there are no valid uses of feature X…– Michael Kenzel
Jan 1 at 15:09
Again, there are some things that can only be achieved via
reinterpret_cast
. For example, how would you cast a pointer to an integer and back without reinterpret_cast
? How would you cast a function pointer to a different function pointer type and back? Just because there are cases where reinterpret_cast
is effectively just a shorthand or just one of several ways to get the same result doesn't mean that there's no justification for it to exist at all. Just because not every possible use of feature X is also a valid use of that feature doesn't mean there are no valid uses of feature X…– Michael Kenzel
Jan 1 at 15:09
1
1
"The lifetime of an object of type T begins when: (1.1) storage with the proper alignment and size for type T is obtained" So there is an object there.
– curiousguy
Jan 2 at 16:22
"The lifetime of an object of type T begins when: (1.1) storage with the proper alignment and size for type T is obtained" So there is an object there.
– curiousguy
Jan 2 at 16:22
2
2
@curiousguy sorry, I have no idea what you're talking about. There can only be one complete object alive in any piece of memory at any given moment. As soon as another object is created in some piece of storage, the lifetime of whatever object may currently reside in there ends [basic.life]/1.4. One of the defining characteristics of the very concept of an object is that an object has an identity. In the C++ object model, that means that no two complete objects can have the same address [intro.object]/9.
– Michael Kenzel
Jan 3 at 1:36
@curiousguy sorry, I have no idea what you're talking about. There can only be one complete object alive in any piece of memory at any given moment. As soon as another object is created in some piece of storage, the lifetime of whatever object may currently reside in there ends [basic.life]/1.4. One of the defining characteristics of the very concept of an object is that an object has an identity. In the C++ object model, that means that no two complete objects can have the same address [intro.object]/9.
– Michael Kenzel
Jan 3 at 1:36
4
4
I don't understand how a philosophical discussion on the potential merits or lack thereof of the C++ object model is relevant to the question or my answer here. The quote above concerns the lifetime of an object. Before you can talk about the lifetime of an object, you first need an object to talk about. [basic.memobj]/1 clearly specifies what language constructs give rise to objects. Just because the C++ standard is not absolutely flawless doesn't mean that nothing can be said about anything and any interpretation is equally valid…
– Michael Kenzel
Jan 3 at 2:26
I don't understand how a philosophical discussion on the potential merits or lack thereof of the C++ object model is relevant to the question or my answer here. The quote above concerns the lifetime of an object. Before you can talk about the lifetime of an object, you first need an object to talk about. [basic.memobj]/1 clearly specifies what language constructs give rise to objects. Just because the C++ standard is not absolutely flawless doesn't mean that nothing can be said about anything and any interpretation is equally valid…
– Michael Kenzel
Jan 3 at 2:26
|
show 19 more comments
[basic.lval]/8 says when the behavior will surely be undefined, but this does not necessarily mean that if you do something from the list in [basic.lval]/8 the behavior will be defined.
[basic.lval]/8 hasn't been changed much since C++98 and it has inaccurate wording such as the use of an undefined term "dynamic type of the object". (C++ defines dynamic types for expressions).
Behavior definedness, in case you do something allowed by [basic.lval]/8, depends on other parts of the standard. Even if it might be agreed that the result of signed/unsigned reinterpretation could be derived from wording in [basic.types], I can't imagine how is it possible to predict the result of an access to an object containing references or virtual methods through char glvalue.
C++17's new pointer and glvalue casting rules made [basic.lval]/8 more useless, because now it is not formally possible to achieve the aims [basic.lval]/8 intended to guarantee (for example, to read the bytes in an object through char
glvalue). As you pointed out, per [expr.reinterpret.cast]/7, after reinterpret_cast to a reference to T, the resulting glvalue still refers to the object the argument of reinterpret_cast referred to.
Per [conv.lval]/(3.4), the result of the lvalue-to-rvalue conversion is the value contained in the object to which the converted glvalue refers. For example, these rules mean that the result of the lvalue-to-rvalue conversion applied to reinterpret_cast<char&>(i)
, where i
is an int
variable, is the value stored in the i
int
object. The type of the prvalue is char
([conv.lval]/1) and if the value of i
is not representable by char
, according to [expr]/1 the behavior is undefined. Trying to read an int
object through char
glvalue will result in UB if the value of the object is not representable by char
, even though this access is "allowed" by [basic.lval]/(8.8). This proves what have been said in the first paragraph.
C++ specification at the fundamental level looks like a trainwreck
– curiousguy
Jan 3 at 20:21
@curiousguy fixed the answer. Actually, you don't get the value of anint
object if it is not representable bychar
type, this would be UB.
– Language Lawyer
Jan 7 at 6:09
add a comment |
[basic.lval]/8 says when the behavior will surely be undefined, but this does not necessarily mean that if you do something from the list in [basic.lval]/8 the behavior will be defined.
[basic.lval]/8 hasn't been changed much since C++98 and it has inaccurate wording such as the use of an undefined term "dynamic type of the object". (C++ defines dynamic types for expressions).
Behavior definedness, in case you do something allowed by [basic.lval]/8, depends on other parts of the standard. Even if it might be agreed that the result of signed/unsigned reinterpretation could be derived from wording in [basic.types], I can't imagine how is it possible to predict the result of an access to an object containing references or virtual methods through char glvalue.
C++17's new pointer and glvalue casting rules made [basic.lval]/8 more useless, because now it is not formally possible to achieve the aims [basic.lval]/8 intended to guarantee (for example, to read the bytes in an object through char
glvalue). As you pointed out, per [expr.reinterpret.cast]/7, after reinterpret_cast to a reference to T, the resulting glvalue still refers to the object the argument of reinterpret_cast referred to.
Per [conv.lval]/(3.4), the result of the lvalue-to-rvalue conversion is the value contained in the object to which the converted glvalue refers. For example, these rules mean that the result of the lvalue-to-rvalue conversion applied to reinterpret_cast<char&>(i)
, where i
is an int
variable, is the value stored in the i
int
object. The type of the prvalue is char
([conv.lval]/1) and if the value of i
is not representable by char
, according to [expr]/1 the behavior is undefined. Trying to read an int
object through char
glvalue will result in UB if the value of the object is not representable by char
, even though this access is "allowed" by [basic.lval]/(8.8). This proves what have been said in the first paragraph.
C++ specification at the fundamental level looks like a trainwreck
– curiousguy
Jan 3 at 20:21
@curiousguy fixed the answer. Actually, you don't get the value of anint
object if it is not representable bychar
type, this would be UB.
– Language Lawyer
Jan 7 at 6:09
add a comment |
[basic.lval]/8 says when the behavior will surely be undefined, but this does not necessarily mean that if you do something from the list in [basic.lval]/8 the behavior will be defined.
[basic.lval]/8 hasn't been changed much since C++98 and it has inaccurate wording such as the use of an undefined term "dynamic type of the object". (C++ defines dynamic types for expressions).
Behavior definedness, in case you do something allowed by [basic.lval]/8, depends on other parts of the standard. Even if it might be agreed that the result of signed/unsigned reinterpretation could be derived from wording in [basic.types], I can't imagine how is it possible to predict the result of an access to an object containing references or virtual methods through char glvalue.
C++17's new pointer and glvalue casting rules made [basic.lval]/8 more useless, because now it is not formally possible to achieve the aims [basic.lval]/8 intended to guarantee (for example, to read the bytes in an object through char
glvalue). As you pointed out, per [expr.reinterpret.cast]/7, after reinterpret_cast to a reference to T, the resulting glvalue still refers to the object the argument of reinterpret_cast referred to.
Per [conv.lval]/(3.4), the result of the lvalue-to-rvalue conversion is the value contained in the object to which the converted glvalue refers. For example, these rules mean that the result of the lvalue-to-rvalue conversion applied to reinterpret_cast<char&>(i)
, where i
is an int
variable, is the value stored in the i
int
object. The type of the prvalue is char
([conv.lval]/1) and if the value of i
is not representable by char
, according to [expr]/1 the behavior is undefined. Trying to read an int
object through char
glvalue will result in UB if the value of the object is not representable by char
, even though this access is "allowed" by [basic.lval]/(8.8). This proves what have been said in the first paragraph.
[basic.lval]/8 says when the behavior will surely be undefined, but this does not necessarily mean that if you do something from the list in [basic.lval]/8 the behavior will be defined.
[basic.lval]/8 hasn't been changed much since C++98 and it has inaccurate wording such as the use of an undefined term "dynamic type of the object". (C++ defines dynamic types for expressions).
Behavior definedness, in case you do something allowed by [basic.lval]/8, depends on other parts of the standard. Even if it might be agreed that the result of signed/unsigned reinterpretation could be derived from wording in [basic.types], I can't imagine how is it possible to predict the result of an access to an object containing references or virtual methods through char glvalue.
C++17's new pointer and glvalue casting rules made [basic.lval]/8 more useless, because now it is not formally possible to achieve the aims [basic.lval]/8 intended to guarantee (for example, to read the bytes in an object through char
glvalue). As you pointed out, per [expr.reinterpret.cast]/7, after reinterpret_cast to a reference to T, the resulting glvalue still refers to the object the argument of reinterpret_cast referred to.
Per [conv.lval]/(3.4), the result of the lvalue-to-rvalue conversion is the value contained in the object to which the converted glvalue refers. For example, these rules mean that the result of the lvalue-to-rvalue conversion applied to reinterpret_cast<char&>(i)
, where i
is an int
variable, is the value stored in the i
int
object. The type of the prvalue is char
([conv.lval]/1) and if the value of i
is not representable by char
, according to [expr]/1 the behavior is undefined. Trying to read an int
object through char
glvalue will result in UB if the value of the object is not representable by char
, even though this access is "allowed" by [basic.lval]/(8.8). This proves what have been said in the first paragraph.
edited Jan 7 at 6:03
answered Jan 3 at 5:55


Language LawyerLanguage Lawyer
397118
397118
C++ specification at the fundamental level looks like a trainwreck
– curiousguy
Jan 3 at 20:21
@curiousguy fixed the answer. Actually, you don't get the value of anint
object if it is not representable bychar
type, this would be UB.
– Language Lawyer
Jan 7 at 6:09
add a comment |
C++ specification at the fundamental level looks like a trainwreck
– curiousguy
Jan 3 at 20:21
@curiousguy fixed the answer. Actually, you don't get the value of anint
object if it is not representable bychar
type, this would be UB.
– Language Lawyer
Jan 7 at 6:09
C++ specification at the fundamental level looks like a trainwreck
– curiousguy
Jan 3 at 20:21
C++ specification at the fundamental level looks like a trainwreck
– curiousguy
Jan 3 at 20:21
@curiousguy fixed the answer. Actually, you don't get the value of an
int
object if it is not representable by char
type, this would be UB.– Language Lawyer
Jan 7 at 6:09
@curiousguy fixed the answer. Actually, you don't get the value of an
int
object if it is not representable by char
type, this would be UB.– Language Lawyer
Jan 7 at 6:09
add a comment |
A reference built using reinterpret_cast
(I include casting a pointer and then dereferencing) can be roundtripped to the original type, if the intermediate types had equal or less stringent alignment requirements.
Most other uses are undefined behavior, due to the strict aliasing rule. (no language citation needed because the question already quotes it)
Notable legal cases where the expression final type does not match the object's dynamic type include aliasing through narrow character types, and the common initial sequence rule for structures.
add a comment |
A reference built using reinterpret_cast
(I include casting a pointer and then dereferencing) can be roundtripped to the original type, if the intermediate types had equal or less stringent alignment requirements.
Most other uses are undefined behavior, due to the strict aliasing rule. (no language citation needed because the question already quotes it)
Notable legal cases where the expression final type does not match the object's dynamic type include aliasing through narrow character types, and the common initial sequence rule for structures.
add a comment |
A reference built using reinterpret_cast
(I include casting a pointer and then dereferencing) can be roundtripped to the original type, if the intermediate types had equal or less stringent alignment requirements.
Most other uses are undefined behavior, due to the strict aliasing rule. (no language citation needed because the question already quotes it)
Notable legal cases where the expression final type does not match the object's dynamic type include aliasing through narrow character types, and the common initial sequence rule for structures.
A reference built using reinterpret_cast
(I include casting a pointer and then dereferencing) can be roundtripped to the original type, if the intermediate types had equal or less stringent alignment requirements.
Most other uses are undefined behavior, due to the strict aliasing rule. (no language citation needed because the question already quotes it)
Notable legal cases where the expression final type does not match the object's dynamic type include aliasing through narrow character types, and the common initial sequence rule for structures.
answered Jan 8 at 19:56


Ben VoigtBen Voigt
236k29314574
236k29314574
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53995657%2fis-reinterpret-cast-type-punning-actually-undefined-behavior%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
4
I haven't noticed that belief being held even narrowly.
– molbdnilo
Jan 1 at 13:19
1
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". No, you're wrong here. I don't understand why you think this. If you access the result, you access it through the type
T
, not the dynamic type ofv
.– geza
Jan 1 at 14:19
1
A glvalue does't have a dynamic type. A glvalue is a value and values just have a type. The only kind of entity that has a dynamic type is an object. A value is not an object.
– Michael Kenzel
Jan 1 at 14:35
1
No object of type
T
exists (reinterpret_cast does not create an object)– M.M
Jan 3 at 7:35
4
@MichaelKenzel No; dynamic type is a property that an expression has (it's not a property of an object). An object just has its type. An expression may have different dynamic type to its static type , in the case that the expression designates a base class subobject
– M.M
Jan 3 at 7:38