casting via void* instead of using reinterpret_cast [duplicate]
Asked Answered
Q

3

37

I'm reading a book and I found that reinterpret_cast should not be used directly, but rather casting to void* in combination with static_cast:

T1 * p1=...
void *pv=p1;
T2 * p2= static_cast<T2*>(pv);

Instead of:

T1 * p1=...
T2 * p2= reinterpret_cast<T2*>(p1);

However, I can't find an explanation why is this better than the direct cast. I would very appreciate if someone can give me an explanation or point me to the answer.

Thanks in advance

p.s. I know what is reinterpret_cast used for, but I never saw that is used in this way

Quitt answered 7/12, 2009 at 21:36 Comment(5)
Thanks for asking this. It seems the answer is burried deep in the Standard. (asked it in usenet several times, noone could point out a guarantee about what that static-cast sequence does better). There has to be something: C++0x added some wording to the spec of reinterpret_cast, which rewrites it when used with certain types to that static_cast sequence.Michamichael
@sinec I have to ask why do you find it necessary to perform such casts? I've written shel loads of C++ code over the years without needing to do so.Caniff
@Neil Butterworth I worked on a project (it was more like adding new features to existing code) and there were lot of crazy casts, but that was inevitable since we couldn't change a legacy code. Anyway, I asked this question since I'm reading a book and I couldn't find an explanation for this.Quitt
Which book? Anyway, get a better book.Laritalariviere
The book was C++ coding standards (Sutter/Alexandrescu) - type safety part (91th chapter).Quitt
J
26

For types for which such cast is permitted (e.g. if T1 is a POD-type and T2 is unsigned char), the approach with static_cast is well-defined by the Standard.

On the other hand, reinterpret_cast is entirely implementation-defined - the only guarantee that you get for it is that you can cast a pointer type to any other pointer type and then back, and you'll get the original value; and also, you can cast a pointer type to an integral type large enough to hold a pointer value (which varies depending on implementation, and needs not exist at all), and then cast it back, and you'll get the original value.

To be more specific, I'll just quote the relevant parts of the Standard, highlighting important parts:

5.2.10[expr.reinterpret.cast]:

The mapping performed by reinterpret_cast is implementation-defined. [Note: it might, or might not, produce a representation different from the original value.] ... A pointer to an object can be explicitly converted to a pointer to an object of different type.) Except that converting an rvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value, the result of such a pointer conversion is unspecified.

So something like this:

struct pod_t { int x; };
pod_t pod;
char* p = reinterpret_cast<char*>(&pod);
memset(p, 0, sizeof pod);

is effectively unspecified.

Explaining why static_cast works is a bit more tricky. Here's the above code rewritten to use static_cast which I believe is guaranteed to always work as intended by the Standard:

struct pod_t { int x; };
pod_t pod;
char* p = static_cast<char*>(static_cast<void*>(&pod));
memset(p, 0, sizeof pod);

Again, let me quote the sections of the Standard that, together, lead me to conclude that the above should be portable:

3.9[basic.types]:

For any object (other than a base-class subobject) of POD type T, whether or not the object holds a valid value of type T, the underlying bytes (1.7) making up the object can be copied into an array of char or unsigned char. If the content of the array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value.

The object representation of an object of type T is the sequence of N unsigned char objects taken up by the object of type T, where N equals sizeof(T).

3.9.2[basic.compound]:

Objects of cv-qualified (3.9.3) or cv-unqualified type void* (pointer to void), can be used to point to objects of unknown type. A void* shall be able to hold any object pointer. A cv-qualified or cv-unqualified (3.9.3) void* shall have the same representation and alignment requirements as a cv-qualified or cv-unqualified char*.

3.10[basic.lval]:

If a program attempts to access the stored value of an object through an lvalue of other than one of the following types the behavior is undefined):

  • ...
  • a char or unsigned char type.

4.10[conv.ptr]:

An rvalue of type “pointer to cv T,” where T is an object type, can be converted to an rvalue of type “pointer to cv void.” The result of converting a “pointer to cv T” to a “pointer to cv void” points to the start of the storage location where the object of type T resides, as if the object is a most derived object (1.8) of type T (that is, not a base class subobject).

5.2.9[expr.static.cast]:

The inverse of any standard conversion sequence (clause 4), other than the lvalue-to-rvalue (4.1), array-topointer (4.2), function-to-pointer (4.3), and boolean (4.12) conversions, can be performed explicitly using static_cast.

[EDIT] On the other hand, we have this gem:

9.2[class.mem]/17:

A pointer to a POD-struct object, suitably converted using a reinterpret_cast, points to its initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa. [Note: There might therefore be unnamed padding within a POD-struct object, but not at its beginning, as necessary to achieve appropriate alignment. ]

which seems to imply that reinterpret_cast between pointers somehow implies "same address". Go figure.

Jeaniejeanine answered 7/12, 2009 at 21:40 Comment(5)
But for the result of static_cast, no more guarantee is made about the conversion to void* and back to a different type either. It just says "A value of type pointer to object converted to "pointer to cv void" and back to the original pointer type will have its original value."Michamichael
See the edited answer. There's no explicit statement that "this is okay", but there are numerous references in the standard that strongly imply it to be so. Particularly note that any POD consists of char objects, and static_cast'ing a POD struct pointer to void* produces a void* pointer that points to the first such char object (as no initial padding is allowed in POD structs).Jeaniejeanine
I.e. if we somehow obtain a pointer to first char of representation of the object separately (without static_cast) and cast that to void*, then Standard indirectly requires it to be equal to pointer to object itself cast to void*. From that follows that, just as we can back-cast the former pointer to char* and have it work, so we can "back-cast" the latter pointer to char* and have it work too (as it's the same pointer value!).Jeaniejeanine
... however, see also the most recent edit. This is convoluted beyond belief, and I'm inclined to say now that, under logic in my previous comments, both static_cast and reinterpret_cast are in fact guaranteed to work.Jeaniejeanine
-1 "there are numerous references in the standard that strongly imply it to be so." All these references don't even mention static_cast.Laritalariviere
L
6

There is not the slightest doubt that the intent is that both forms are well defined, but the wording fails to capture that.

Both forms will work in practice.

reinterpret_cast is more explicit about the intent and should be preferred.

Laritalariviere answered 13/12, 2011 at 8:17 Comment(0)
S
4

The real reason this is so is because of how C++ defines inheritance, and because of member pointers.

With C, pointer is pretty much just an address, as it should be. In C++ it has to be more complex because of some of its features.

Member pointers are really an offset into a class, so casting them is always a disaster using C style.

If you have multiply inherited two virtual objects that also have some concrete parts, that's also a disaster for C style. This is the case in multiple inheritance that causes all the problems, though, so you should not ever want to use this anyway.

Really hopefully you never use these cases in the first place. Also, if you are casting a lot that's another sign you are messing up in in your design.

The only time I end up casting is with the primitives in areas C++ decides are not the same but where obviously they have to be. For actual objects, any time you want to cast something, start to question your design because you should be 'programming to the interface' most of the time. Of course, you can't change how 3rd party APIs work so you don't always have much choice.

Silvana answered 8/12, 2009 at 1:3 Comment(1)
I agree with most of what you've said here, but the negative stigma on casting may be a bit much. Casting has its place (like most other language features), and based on some recent work I've done some useful examples include: using const_cast in a non-const operator overload to call the const overload (where the logic is the same), invoking a conversion operator to prevent code duplication, resolving ambiguity (outputting small integer literals vs. character literals), and getting the true address of an object with overloaded address-of operator. To every feature there is a season I guess.Ordonez

© 2022 - 2024 — McMap. All rights reserved.