Why do we have reinterpret_cast in C++ when two chained static_cast can do its job?
Asked Answered
P

7

44

Say I want to cast A* to char* and vice-versa, we have two choices (I mean, many of us think we've two choices, because both seems to work! Hence the confusion!):

struct A
{
    int age;
    char name[128];
};

A a;
char *buffer = static_cast<char*>(static_cast<void*>(&a)); //choice 1
char *buffer = reinterpret_cast<char*>(&a); //choice 2

Both work fine.

//convert back
A *pA = static_cast<A*>(static_cast<void*>(buffer)); //choice 1
A *pA = reinterpret_cast<A*>(buffer); //choice 2

Even this works fine!

So why do we have reinterpret_cast in C++ when two chained static_cast can do its job?

Some of you might think this topic is a duplicate of the previous topics such as listed at the bottom of this post, but it's not. Those topics discuss only theoretically, but none of them gives even a single example demonstrating why reintepret_cast is really needed, and two static_cast would surely fail. I agree, one static_cast would fail. But how about two?

If the syntax of two chained static_cast looks cumbersome, then we can write a function template to make it more programmer-friendly:

template<class To, class From>
To any_cast(From v)
{
    return static_cast<To>(static_cast<void*>(v));
}

And then we can use this, as:

char *buffer = any_cast<char*>(&a); //choice 1
char *buffer = reinterpret_cast<char*>(&a); //choice 2

//convert back
A *pA = any_cast<A*>(buffer); //choice 1
A *pA = reinterpret_cast<A*>(buffer); //choice 2

Also, see this situation where any_cast can be useful: Proper casting for fstream read and write member functions.

So my question basically is,

  • Why do we have reinterpret_cast in C++?
  • Please show me even a single example where two chained static_cast would surely fail to do the same job?

Paleethnology answered 17/2, 2011 at 6:43 Comment(3)
Emmm... I need a pointer of type T* with address 8 (for this trick #5014561) - how do I do that with a static_cast?Mulderig
@sharptooth: I didn't really understand that. Please post an answer explaining and demonstrating two chained static_cast would fail to do the job of reinterpret_cast.Paleethnology
@Nawaz - I don't like you're "cast-style" template function any_cast, because it's name is misleadingly. It will do a good job if you're casting from type pointer-to-A to pointer-to-B, but not in general ("any" cases).Mellicent
D
36

There are things that reinterpret_cast can do that no sequence of static_casts can do (all from C++03 5.2.10):

  • A pointer can be explicitly converted to any integral type large enough to hold it.

  • A value of integral type or enumeration type can be explicitly converted to a pointer.

  • A pointer to a function can be explicitly converted to a pointer to a function of a different type.

  • An rvalue of type "pointer to member of X of type T1" can be explicitly converted to an rvalue of type "pointer to member of Y of type T2" if T1 and T2 are both function types or both object types.

Also, from C++03 9.2/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.
Dragonnade answered 17/2, 2011 at 6:52 Comment(14)
@James: please write a small code to demonstrate what you're saying. I seriously need to understand this. :-)Paleethnology
@Nawaz: int i = 42; char* p; Now p = static_cast<X>(static_cast<Y>(i)); will fail compilation for all possible values of X and Y (unless they are class types that provide conversion functions).Massive
also, as fifth bullet point, pointer to first element of POD struct can be converted to pointer to the struct, and vice versa.Turkoman
@Alf: Can't that be done via static_cast<M*>(static_cast<void*>(p)) where p is a pointer to the struct-type object and M is the type of the initial member?Dragonnade
@All: Nice comments, and nice answer. I'm learning new things. Please post more :DPaleethnology
@Nawaz: I think the first two are straightforward; in any case, @sharptooth's answer gives an example of the second case and the first is just the reverse of that. I can't think of compelling examples for the latter two: it's easy to construct examples for them, but I don't know about real, useful examples. These items just form "a list of things that reinterpret_cast can do but no sequence of static_casts can do."Dragonnade
@James: AFAIK not in C++98/03. the standard explicitly guarantees the conversion for reinterpret_cast, though.Turkoman
@James: Do you know any book or article that explains and lists what only reinterpret_cast can do but a sequence of static_cast cannot?Paleethnology
@Alf P. Steinbach: That's interesting. Can you provide me the reference from the Standard?Paleethnology
@Nawaz: as I recall it's at the end of the classes section intro. see my old blog posting alfps.wordpress.com/2010/06/14/…Turkoman
@Alf: Oh, yes, at 9.2/17. That's very convenient that that language isn't included in 5.2.10 with the rest of the reinterpret_cast specification! :-) Thanks for the heads-up.Dragonnade
@Nawaz: No, I don't know of any article; would a hardcopy of the C++ Standard count as a book? :-) I haven't really read any C++ texts that would cover this sort of thing, though, other than the language standard itself.Dragonnade
@James: Yes. Hardcopy of C++ Standard would count as a book. Even a softcopy, for that matter. Which section(s) from it, explains these?Paleethnology
@Nawaz: In the answer I reference 5.2.10, which defines the behavior of reinterpret_cast, and 9.2/17, which (as @Alf so helpfully pointed out) defines more behavior for reinterpret_cast. All four of the cast operators are defined in 5.2 and the C-style cast and its special powers are covered in 5.4.Dragonnade
M
16

You need reinterpret_cast to get a pointer with a hardcoded address (like here):

int* pointer = reinterpret_cast<int*>( 0x1234 );

you might want to have such code to get to some memory-mapped device input-output port.

Mulderig answered 17/2, 2011 at 6:46 Comment(7)
And that cannot be done with chained static_cast? Anyone tried that?Paleethnology
@nawaz: I don't know how to do that with static_cast and I've never seen anyone doing that - either C-style cast or reinterpret_cast is used.Mulderig
@sharptooth: I mean, using any_cast (as defined in my question)? Would it fail?Paleethnology
@Nawaz: What's any_cast? You can use either reinterpret_cast or C-style cast here.Mulderig
@sharptooth: Looks like you didn't understand the point of the question.Paleethnology
@Nawaz: This perfectly answers your question. Your any_cast does not work in this case. You cannot static_cast an int to a void*: "error: invalid static_cast from type 'int' to type 'void*'"Aloisius
@nobar: Yes, I tested that. static_cast doesn't work here. :-)Paleethnology
M
6

A concrete example:

char a[4] = "Hi\n";
char* p = &a;

f(reinterpret_cast<char (&)[4]>(p));  // call f after restoring full type
      // ^-- any_cast<> can't do this...

// e.g. given...
template <typename T, int N>   // <=--- can match this function
void f(T (&)[N]) { std::cout << "array size " << N << '\n'; }
Mixtec answered 17/2, 2011 at 7:33 Comment(0)
K
6

Other than the practical reasons that others have given where there is a difference in what they can do it's a good thing to have because its doing a different job.

static_cast is saying please convert data of type X to Y. reinterpret_cast is saying please interpret the data in X as a Y.

It may well be that the underlying operations are the same, and that either would work in many cases. But there is a conceptual difference between saying please convert X into a Y, and saying "yes I know this data is declared as a X but please use it as if it was really a Y".

Katabasis answered 17/2, 2011 at 8:29 Comment(4)
This sentence "static_cast is saying please convert data of type X to Y. reinterpret_cast is saying please interpret the data in X as a Y." does not help understanding, as it begs another question : what is the difference between "convert" and "interpret" in this context?Paleethnology
Conversion implies a programmed conversion, such as converting a float value to an integer . "interpret" means "I know I said this is a pointer to a int but actually it's not that it's a pointer to a float so please treat it as such"Katabasis
So what? How exactly it makes any difference as fas as understanding the difference is concerned? We're know learning english here. One can say "I know this is int but interpret this as float" when he cast using static_cast. Does it help?Paleethnology
+1 - that's conceptually key, although there are workarounds e.g. given int x you can reinterpret_cast<float&>(x), can not static_cast<float&>(x), but can chain ala *static_cast<float*>(static_cast<void*>(&x)) (notice any_cast isn't a clean substitute: you need an extra deref). Crucially, static_cast<float>(x) does something very different: rounding as the value is converted from int.Mixtec
C
3

As far as I can tell your choice 1 (two chained static_cast) is dreaded undefined behaviour. Static cast only guarantees that casting pointer to void * and then back to original pointer works in a way that the resulting pointer from these to conversions still points to the original object. All other conversions are UB. For pointers to objects (instances of the user defined classes) static_cast may alter the pointer value.

For the reinterpret_cast - it only alters the type of the pointer and as far as I know - it never touches the pointer value.

So technically speaking the two choices are not equivalent.

EDIT: For the reference, static_cast is described in section 5.2.9 of current C++0x draft (sorry, don't have C++03 standard, the draft I consider current is n3225.pdf). It describes all allowed conversions, and I guess anything not specifically listed = UB. So it can blow you PC if it chooses to do so.

Covenantee answered 17/2, 2011 at 9:35 Comment(5)
undefined behaviour in what case? It depends on the datatypes also!Paleethnology
Standard guarantees that type * p and type * q = static_cast<type *>(static_cast<void *>(p)) point to the same object. Now doing static_cast<type2 *>(static_cast<void *>(p)) where type2 if different to type is UB (although it USUALLY works). If you try chain of static casts with inheritance (especially multiple one) you WILL see a difference.Covenantee
This is a great point! If a class uses multiple inheritance, a static-cast to a base type pointer doesn't only change the type -- it will (in some cases) also change the value. So static_cast is fundamentally different than reinterpret_cast in this example. However, the way @Nawaz is doing it (going first to void*), his "any_cast" probably still works for this.Aloisius
static_cast and reinterpret_cast both suffer from the SAME potential for undefined behavior when casting from a void* to a pointer of another type. So again, "any_cast" works fine as a replacement for reinterpret_cast for this scenario.Aloisius
"it never touches the pointer value." That's the intent, but the standard committee forgot to write it down. The intent is also that 2 static = 1 reinterpret. The committee also forgot.Nahshun
B
0

Using of C Style casting is not safer. It never checks for different types can be mixed together. C++ casts helps you to make sure the type casts are done as per related objects (based on the cast you use). This is the more recommended way to use casts than using the traditional C Style casts that's always harmful.

Beautifully answered 11/10, 2014 at 6:59 Comment(0)
A
-1

Look, people, you don't really need reinterpret_cast, static_cast, or even the other two C++ styles casts (dynamic* and const).

Using a C style cast is both shorter and allows you to do everything the four C++-style cast let you do.

anyType someVar = (anyOtherType)otherVar;

So why use the C++-style casts? Readability. Secondly: because the more restrictive casts allow more code safety.

*okay, you might need dynamic

Alodium answered 17/2, 2011 at 7:4 Comment(4)
C-style cast cannot do the job of dynamic_cast!Paleethnology
This isn't about dynamic_cast though. Its about c, static and reinterpret. Besides, dynamic relies on RTTI.Alodium
You've written " you don't really need reinterpret_cast, static_cast, or even the other two C++ styles casts (dynamic and const)."Paleethnology
and -1 for advocating, in addition to simply not using recommended idioms of the language, obscuring the programmer's intentions and the possibly resulting nightmarish debugging processesBillboard

© 2022 - 2024 — McMap. All rights reserved.