Skip to main content
GameDev.net gamedev.net
🔒 Locked

C++, casting and const

Started by Ryan_001 Aug 24, 2010 at 7:19 PM 17 replies 2.5k views
Original Post
Ryan_001
Ryan_001
Can I use reinterpret_cast to add or remove const-ness? Now I know const_cast can, but more specifically is something like the following kosher?

void PrintInt (const void* p) {   cout << *reinterpret_cast<const int*>(p) << endl;   }int a = 5;const int b = 6;PrintInt(&a);PrintInt(&b);


From what I understand, reinterpret_cast is only valid if you cast the pointer back to its original type, but does the const modifier play into this (and what about volatile)? The above code example works in MSVC, but just cause it works doesn't mean its proper C++.
SiCrane
SiCrane
Adding const doesn't require any sort of cast. reinterpret_cast can't remove const; a const_cast is required.
Red Ant
Red Ant
Beware that casting to or from void* is almost never a good idea since it completely chucks type safety out the window.
CmpDev
CmpDev
Quote:
Original post by Ryan_001
Can I use reinterpret_cast to add or remove const-ness? Now I know const_cast can, but more specifically is something like the following kosher?

void PrintInt (const void* p) {   cout << *reinterpret_cast<const int*>(p) << endl;   }int a = 5;const int b = 6;PrintInt(&a);PrintInt(&b);


From what I understand, reinterpret_cast is only valid if you cast the pointer back to its original type, but does the const modifier play into this (and what about volatile)? The above code example works in MSVC, but just cause it works doesn't mean its proper C++.

As pointed out reinterpret_cast is the wrong operator to use here and Sicrane has already pointed out const_cast but that may require using along with static_cast. Currently your code is undefined/implementation specific; reinterpret_cast is rather limited in what it guarantees and allows.

Quote:
but does the const modifier play into this (and what about volatile)?

With respect to the void pointer, no this does not have any effect but it does have an effect for the type before it was implicitly cast to void const*. You are not casting back to the orignal type.

Don't use void pointer unless you must, I see no reason to use it here as the following does what you want.
std::cout <<(&type) <<std::endl;


RobTheBloke
RobTheBloke
Quote:
Original post by Red Ant
Beware that casting to or from void* is almost never a good idea since it completely chucks type safety out the window.


Uhm. All casts throw away type safety. That's it's purpose in life. To throw away the type safety of the language because *you know better*.... ;)
BitMaster
BitMaster
Anything you do via static_cast in C++ does not throw away type safety (in general it's just there to avoid a warning).

Anything you do via const_cast does not throw away type safety (provided you can guarantee the data is never written to, so for example a compatibility layer with C code can use it).

dynamic_cast does not throw away type safety (and cannot work with a void* pointer to begin with).

The only place where type safety goes out the window is the dreaded reinterpret_cast.
RobTheBloke
RobTheBloke
Quote:
Original post by CmpDev
As pointed out reinterpret_cast is the wrong operator to use here and Sicrane has already pointed out const_cast but that may require using along with static_cast.


No he didn't. He said const_cast is required to remove const. Since this case doesn't require the removal of const, the correct cast would be static_cast.

Quote:
Original post by CmpDev
Currently your code is undefined/implementation specific; reinterpret_cast is rather limited in what it guarantees and allows.


Uhm, reinterpret_cast has no limitations. It's the most limitation free of all the casts. It's not undefined behaviour either, quite the opposite infact... There will be an integer at the memory location referenced by p (although care has to be taken to ensure you use the function correctly since it's without type checking).

Quote:
Original post by CmpDev
Quote:
but does the const modifier play into this (and what about volatile)?

With respect to the void pointer, no this does not have any effect but it does have an effect for the type before it was implicitly cast to void const*. You are not casting back to the orignal type.


void const* and const void* are not the same types. volatile does not come into play here (and won't do unless you start using multiple threads).

Quote:
Original post by CmpDev
Don't use void pointer unless you must, I see no reason to use it here as the following does what you want.
*** Source Snippet Removed ***


Lol. It's definitely not what he wants....
RobTheBloke
RobTheBloke
Quote:
Original post by BitMaster
Anything you do via static_cast in C++ does not throw away type safety (in general it's just there to avoid a warning).


Nonsense. It throws away type safety.

class Foo{};class Bar : public Foo{  float a;};class Car : public Foo{  int a;};Foo* f = new Bar();Car* c = static_cast<Car*>(f);


Quote:
Original post by BitMaster
Anything you do via const_cast does not throw away type safety (provided you can guarantee the data is never written to, so for example a compatibility layer with C code can use it).


removing const is *only* ever needed when you need to write to the variable (otherwise you wouldn't need to get rid of const). const is part of C by the way (just have a look in gl.h, stdlib.h). So by removing the const you are throwing away the type safety (since a programmer will assume that const args passed to a function will not be modified).

Quote:
Original post by BitMaster
dynamic_cast does not throw away type safety (and cannot work with a void* pointer to begin with).


True, but I prefer to pretend that one doesn't exist (there are better ways to perform RTTI that don't require tonnes of strcmps). All games will have RTTI disabled...

Quote:
The only place where type safety goes out the window is the dreaded reinterpret_cast.


And static cast, and const cast, and the C cast (re-interpret under a different name).
CmpDev
CmpDev
Quote:
Original post by RobTheBloke
Quote:
Original post by CmpDev
As pointed out reinterpret_cast is the wrong operator to use here and Sicrane has already pointed out const_cast but that may require using along with static_cast.


No he didn't. He said const_cast is required to remove const. Since this case doesn't require the removal of const, the correct cast would be static_cast.

I do not know what you are disagreeing with here, reinterpret_cast is the wrong one to use.
Quote:
Original post by RobTheBloke
Quote:
Original post by CmpDev
Currently your code is undefined/implementation specific; reinterpret_cast is rather limited in what it guarantees and allows.


Uhm, reinterpret_cast has no limitations. It's the most limitation free of all the casts. It's not undefined behaviour either, quite the opposite infact... There will be an integer at the memory location referenced by p (although care has to be taken to ensure you use the function correctly since it's without type checking).

IIRC reinterpret_cast has the least set of circumstances which is defined, any type of cast not mentioned in the standard is either UB or IS. Cast from one pointer type to void pointer and then to a different pointer is not defined at best is IS.

Quote:
Original post by RobTheBloke
Quote:
Original post by CmpDev
Quote:
but does the const modifier play into this (and what about volatile)?

With respect to the void pointer, no this does not have any effect but it does have an effect for the type before it was implicitly cast to void const*. You are not casting back to the orignal type.


void const* and const void* are not the same types.
[/quote]
Ok you have lost me here they are the same!
Quote:
Original post by RobTheBloke
volatile does not come into play here (and won't do unless you start using multiple threads).

volatile has nothing todo with threads but that is another topic.

Quote:
Original post by RobTheBloke
Quote:
Original post by CmpDev
Don't use void pointer unless you must, I see no reason to use it here as the following does what you want.
*** Source Snippet Removed ***


Lol. It's definitely not what he wants....


I hold my hand up for that small mistake, I could laugh at the many you have just written.
Shinkage
Shinkage
Quote:
Original post by RobTheBloke
void const* and const void* are not the same types.

Just wanted to make the one comment that these are in fact exactly the same type, and that "void * const" would probably be the different type you're thinking of.

Edit: For reference http://www.parashift.com/c++-faq-lite/const-correctness.html#faq-18.9

Edit2: Okay, this too...
Quote:
Original post by CmpDev
volatile has nothing todo with threads but that is another topic.

This isn't true either. Volatile specifically says "other threads/processes may modify this" so that the compiler won't screw things up with optimizations.
Red Ant
Red Ant
Quote:
Original post by Shinkage
Edit2: Okay, this too...
Quote:
Original post by CmpDev
volatile has nothing todo with threads but that is another topic.

This isn't true either. Volatile specifically says "other threads/processes may modify this" so that the compiler won't screw things up with optimizations.


No, it doesn't. Well, it may but that depends on what compiler / OS you're using.


EDIT: Read the "In C and C++" section of this Wikipedia article. http://en.wikipedia.org/wiki/Volatile_variable
Nypyren
Nypyren
Quote:
Original post by RobTheBloke
Quote:
Original post by Red Ant
Beware that casting to or from void* is almost never a good idea since it completely chucks type safety out the window.


Uhm. All casts throw away type safety. That's it's purpose in life. To throw away the type safety of the language because *you know better*.... ;)


Casting to/from a void* is much worse since the compiler will also lose any offset-to-virtual-table-pointer information - this makes it possible to cast to "the right type" in the following order and cause pretty spectacular crashes:

1. Derived* to void*
2. void* to Base*
3. Base* to Derived* <- can shift the actual pointer address to account for a vpointer or multiple-inheritance shift.
Shinkage
Shinkage
Quote:
Original post by Red Ant
Quote:
Original post by Shinkage
Edit2: Okay, this too...
Quote:
Original post by CmpDev
volatile has nothing todo with threads but that is another topic.

This isn't true either. Volatile specifically says "other threads/processes may modify this" so that the compiler won't screw things up with optimizations.


No, it doesn't. Well, it may but that depends on what compiler / OS you're using.


EDIT: Read the "In C and C++" section of this Wikipedia article. http://en.wikipedia.org/wiki/Volatile_variable


Okay, well, in GCC and MSVC it works more or less the way I described. It tells the compiler that "this variable may be modified elsewhere" so the compiler doesn't assume it can just stick it in a register and use it from there. Whether that's because it's mapped to the hardware or because another thread does so, the effect is the same. Not really familiar with any compilers that treat it differently from that, but it definitely does have something to do with threading.

Your Wikipedia citation is true, but somewhat irrelevant. I didn't (and wouldn't) claim that volatile can be relied on for atomic access or proper sequencing, just that it is not unrelated to threads, as had been earlier claimed. User beware.
ApochPiQ
ApochPiQ
Quote:
Original post by Shinkage
Okay, well, in GCC and MSVC it works more or less the way I described. It tells the compiler that "this variable may be modified elsewhere" so the compiler doesn't assume it can just stick it in a register and use it from there. Whether that's because it's mapped to the hardware or because another thread does so, the effect is the same. Not really familiar with any compilers that treat it differently from that, but it definitely does have something to do with threading.

Your Wikipedia citation is true, but somewhat irrelevant. I didn't (and wouldn't) claim that volatile can be relied on for atomic access or proper sequencing, just that it is not unrelated to threads, as had been earlier claimed. User beware.



According to the standard, all volatile does is prevent reordering of instructions that access the variable, and enforces that the variable is read or written to exactly once for every access written in the original code. By the standard, then, volatile indeed has nothing to do with threading.

Anything beyond that is entirely implementation-specific, and the details of how that works may still vary.

For instance, the PowerPC compiler used for the Xbox 360 has no notion of volatile used for threading purposes. Note also that due to things like CPU-level caching, CPU-level instruction reordering within the pipeline, and the way NUMA works, you will not get proper thread safety guarantees even in compilers that claim volatile has some threading-related benefits.

volatile is almost always the wrong thing to use, especially when threading is concerned. Whether or not it has some marginal added semantics in certain compilers does not change this.
Shinkage
Shinkage
Quote:
Original post by ApochPiQ
According to the standard, all volatile does is prevent reordering of instructions that access the variable, and enforces that the variable is read or written to exactly once for every access written in the original code. By the standard, then, volatile indeed has nothing to do with threading.


Just because the standard doesn't say "threading" doesn't mean its application has nothing to do with threading. As an example, I've used the keyword in one situation where I had a separate thread which could be spawned to emulate a memory-mapped interface to a hardware device for testing purposes (that is, so the software could be tested without the device always being there). It was absolutely "threading related," and absolutely the right thing to use at the time.

But all these arguments are getting silly and pedantic. Somebody said it has "nothing to do with threads," and that is demonstrably false in the general case. It may be true in certain architectures, for certain compilers, but it's misleading and just plain wrong to make the claim as an absolute.

If you're saying you shouldn't be using it for any kind of thread-safety guarantees, you'd be entirely right, but nobody ever said you should, so what's the point?
Ryan_001
Ryan_001
This has gone far beyond the original question, that said thanks for all the responses. There seems to be alot of disagreement on what reinterpret_cast can safely do. Now I know shouldn't use it, and I don't in normal code, more I was just curious on what it can do. It seems a bit silly that any use of reinterpret_cast would be undefined/implementation specific, but perhaps I'm wrong here?

I was under the impression casting through reinterpret_cast and back, as long as the alignment and size of the intermediate was appropriate, that you can safely (and in a defined/non-implementation specific way) get the original value back (whatever that may be), and use it. For example:

int a = 5;void* b = &aint c = *reinterpret_cast<int*>(b);


is undefined?

Also this one thing piqued my interest:

Quote:
Original post by ApochPiQ
volatile is almost always the wrong thing to use, especially when threading is concerned.


If volatile is not to be used for inter-thread communication what do you use?

Whether it be message passing, mutex's, critical sections, barriers, or what-have-you, they all rely on volatile under the hood don't they? Apart from going straight down to assembly or calling as OS supplied routine (both of which are undefined/implementation specific) you don't have a lot of other options.

[Edited by - Ryan_001 on August 28, 2010 7:14:06 PM]
ApochPiQ
ApochPiQ
As the documentation indicates, casting back to the original type from void* is perfectly safe and guaranteed to work. Anything else is unsafe.

volatile is not used as a foundation for thread safety primitives. Thread locks, inter-thread/inter-process communication, et. al. require OS-level support. There is no such thing as a portable threading implementation (although portable interfaces such as pthreads and boost's thread library do exist), because under the hood, everything has to have specific support from the multitasking kernel to ensure safety.
MaulingMonkey
MaulingMonkey
Quote:
Original post by Ryan_001
Whether it be message passing, mutex's, critical sections, barriers, or what-have-you, they all rely on volatile under the hood don't they?

Not necessarily.
Quote:
Apart from going straight down to assembly or calling as OS supplied routine (both of which are undefined/implementation specific) you don't have a lot of other options.

No you don't... which is part of the reason that boost's threading library is getting shoved into the standard library of the next revision of C++.
MaulingMonkey
MaulingMonkey
Quote:
Original post by Ryan_001
This has gone far beyond the original question, that said thanks for all the responses. There seems to be alot of disagreement on what reinterpret_cast can safely do. Now I know shouldn't use it, and I don't in normal code, more I was just curious on what it can do. It seems a bit silly that any use of reinterpret_cast would be undefined/implementation specific, but perhaps I'm wrong here?

I was under the impression casting through reinterpret_cast and back, as long as the alignment and size of the intermediate was appropriate, that you can safely (and in a defined/non-implementation specific way) get the original value back (whatever that may be), and use it. For example:

int a = 5;void* b = &aint c = *reinterpret_cast<int*>(b);


is undefined?


That should be standard defined -- but you could just use a static_cast there as well. Using char* instead of void* (and adding another reinterpret_cast) I believe would be similarly defined behavior.

However, you shouldn't be too suprised if something is undefined, 'unspecified', or implementation defined in C++. For example, which is called first, a or b, in this statement?
f( a(), b() );
Trick question: It's unspecified, and may actually change depending on compiler settings.

Yes, even this simple kind of thing. It's quite maddening [lol]. To worsen matters, take this bit of code:
f( ++(*a), ++(*b) );
People will infer from the above question that the order in which these two values are incremented is unspecified. But it gets worse: If a and b point to the same int (or other intrinsic type), the behavior is outright undefined behavior. In practice, the optimizer will often increment twice before passing either value to f. In theory, it could do anything -- even crash, corrupt memory, or launch nuclear missiles at a cow ranch in Alaska. All perfectly acceptable behavior according to the C++ standard [lol]

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.