8

I was just reading this thread: Simple c++ pointer casting

And that got me to thinking why a static_cast between different pointer types is not allowed (except in the cases in which it is) unless you static_cast to a void* as an intermediary step. It seems to me that either both or neither should be allowed. Here is an example:

char*          cs;
unsigned char* ucs;

cs = reinterpret_cast<char*>(ucs);                  // 1) allowed, of course
cs = static_cast<char*>(ucs);                       // 2) not allowed: incompatible pointer types
cs = static_cast<char*>( static_cast<void*>(ucs) ); // 3) now it's allowed!

It seems to me that if #3 is possible then #2 should be allowed as well. Or conversely, if #2 is not allowed on the grounds that the pointers are not compatible (necessitating a reinterpret_cast) then perhaps static_casting from a void* to anything should not be allowed on the grounds of pointer incompatibility. (Casting to a void* from any other pointer is always okay, of course.)

So why isn't one of those possilities true - that #2 and #3 are either both allowed or neither allowed? Why does it instead work as shown in my example?

Community
  • 1
  • 1
Dave Lillethun
  • 2,978
  • 3
  • 20
  • 24
  • Because C++ protects against accident, not from abuse. And C++ values power over safety. – sehe Jul 09 '13 at 21:58
  • Sure, and you can do `const int i = 0; *const_cast(&i) = 1;` and it will compile, yet it's a constraint violation and causes UB. That's the wonderful in `void *`. C++ apparently didn't manage to completely eliminate the type unsafety of C. –  Jul 09 '13 at 21:59
  • @H2CO3 Sure, but that's the kind of unsafety const_cast is meant for. Similarly, `unsigned char*` to `char*` is (apparently) the kind of unsafety reinterpret_cast is meant for (not static_cast)... So I guess my point is, if that's "too unsafe" for static_cast then why doesn't casting a `void*` require a reinterpret_cast as well? – Dave Lillethun Jul 09 '13 at 22:05
  • @DaveLillethun Because `void *` is designed to be compatible with any (data) pointer type. –  Jul 09 '13 at 22:06
  • @DaveLillethun: There is a particular use case that is *valid*: casting from `T*` to `void*` and back to `T*`, among other things to interact with legacy C style APIs. Now once the compiler gets a `void*` it cannot possibly know whether it came from a `char*` or an `unsigned char*`, so it is not required to diagnose the issue. – David Rodríguez - dribeas Jul 09 '13 at 22:20
  • I understand why casting from `void*` to `T*` in general is allowed. However, I still don't see why it doesn't require a `reinterpret_cast` to do so... – Dave Lillethun Jul 09 '13 at 22:55

3 Answers3

4
cs = static_cast<char*>( static_cast<void*>(ucs) ); // 3) now it's allowed!

It will compile. Does it mean that it is allowed? Not. The standard allows conversions from void* to T* only in the case where the void* was obtained by a conversion from T*, which is not your case.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • 1
    Perhaps you can elaborate slightly on what happens if you do the cast, regardless of the restrictions posed in the standard... :) – sehe Jul 09 '13 at 22:00
  • Could you clarify "allowed"? Will this throw a runtime error? – Dave Lillethun Jul 09 '13 at 22:02
  • 2
    It is undefined behavior, and in theory anything can happen. In practice, in your particular case it will most likely work for two reasons. The first is that the types differ only on signed-ness. The second is that `char*` are special and have special treatment in the standard. At any rate, that sequence of `static_cast` is really disallowed and the conversion should be done with `reinterpret_cast`. – David Rodríguez - dribeas Jul 09 '13 at 22:18
  • I'd argue that undefined behavior and disallowed behavior are not the same thing, and in fact are mutually exclusive. That said, I don't disagree that it's a bad idea and that a `reinterpret_cast` is really what's called for... but I guess that's the crux of my question - if this is such a bad idea, why isn't `reinterpret_cast` also required for all conversions of `void*` to any other pointer type? – Dave Lillethun Jul 09 '13 at 22:30
  • @DaveLillethun: I already pointed that out in a comment to the question, as long as you cast back to the same type it is perfectly find and well defined. In particular when interfacing with C libraries that use `void*` as a *generic* type. You can use `static_cast` to convert to and back from `void*`. – David Rodríguez - dribeas Jul 10 '13 at 02:36
  • Well, I think maybe that goes back to what I said in the other answer. The compiler can't tell the difference, so you say "Trust me, this is safe" and the compiler does trust you, because what else is it going to do? By contrast, where static_cast fails is when you say "Trust me, this is safe" but the compiler actually knows it is not. (In those cases, you need to instead say "I know this is unsafe, but do it anyway" which is a reinterpret_cast.) Of course, this still leaves open the problem that we haven't defined "safe"... so until that is done, my answer is really still a non-answer. – Dave Lillethun Jul 10 '13 at 20:56
  • @DavidRodríguez-dribeas: `At any rate, that sequence of static_cast is really disallowed and the conversion should be done with reinterpret_cast.` No, it's completely allowed, because _it does exactly the **same** thing_. `reinterpret_cast` between different pointer types is _defined in terms of_ two nested `static_cast`s. See N3797 s5.2.10 p7: `An object pointer can be explicitly converted to an object pointer of a different type.(n72) When a prvalue v of object pointer type is converted to the object pointer type “pointer to cv T”, the result is static_cast(static_cast(v)).` – underscore_d Jul 28 '16 at 22:22
  • 1
    @underscore_d: Up to, and including, C++11 `static_cast<>` from a `void*` was limited to conversions back to the same type. I have verified that in C++14 and `static_cast` wording has changed and it does allow conversions from `void*` back to a type that was not the original one as long as the alignment requirements are the same. – David Rodríguez - dribeas Aug 01 '16 at 08:44
  • @DavidRodríguez-dribeas Thanks for the clarification; I hadn't realised that was a recent thing. – underscore_d Aug 01 '16 at 10:09
1

The problem is this:

my_type *p = new my_type;
void *vp = p; // okay

Now if you need to get the original pointer back you do this:

my_type *new_p = static_cast<my_type*>(vp); // okay

That's fine, and the result is well defined. You get undefined behavior if you use static_cast to convert the pointer to a different type than the original pointer, and that's not something the compiler can, in general, detect. So the cast is allowed, and if you misuse it, it's on your head.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • I understand what you're saying here, but I still don't understand why it doesn't require you to do `my_type *new_p = reinterpret_cast(vp);` instead of a static_cast... – Dave Lillethun Jul 09 '13 at 22:52
  • `reinterpret_cast` is for funky conversions. `T* -> void* -> T*` is quite common, and not at all funky. That is, a `reinterpret_cast` often does non-portable conversions, and should be a flag for careful review. – Pete Becker Jul 09 '13 at 22:55
  • Could you define "funky"? My gut reaction is that changing signedness isn't "funky" either... but that's based on an imprecise feeling, not a clear definition of the word. – Dave Lillethun Jul 09 '13 at 22:58
1

Okay, I'm going to take a stab at answering my own question here because I think I'm getting an idea, although I'm not sure it's right. You can let me know if you think I'm getting it by up/down voting my answer, I guess. :)

So I think static_cast is telling the compiler, "Trust me, this is safe." Whereas reinterpret_cast is telling the compiler, "I know this is unsafe; do it anyway."

So going back to my examples:

cs = reinterpret_cast<char*>(ucs);

is unsafe, but the compiler will do it anyway because you told it to.

cs = static_cast<char*>(ucs);

will result in a compiler error because you said, "Trust me, this is safe," but the compiler knows that it really is not. So it doesn't trust you because it actually knows in this case, and therefore errors.

Now the last example has two parts:

void* temp = static_cast<void*>(ucs);

is allowed of course because casting to void* is safe.

cs = static_case<char*>(temp); // remember temp is a void*

is allowed because casting from a void* is sometimes safe and sometimes not - the compiler cannot tell which it is in any given instance... so when you say, "Trust me, this is safe," the compiler trusts you - and if you're wrong, then that's on your head. O.O

So in other words, you cannot static_cast something that is never a safe case - that's when you need reinterpret_cast ... but you can static_cast something that is sometime safe and sometimes not (and the compiler can't tell which is the case in any given instance).

All that said... I refered to "safe" and "unsafe" casts a lot here, but what do those words actually mean? (And so, to some extent, I still don't even understand my own answer here...! Please comment if you can help me figure that out...)

Dave Lillethun
  • 2,978
  • 3
  • 20
  • 24
  • 1
    This is not entirely true. The *safety* of casting `signed char*` to `unsigned char*` or `char*` could be argued on different grounds, but the standard explicitly allows aliasing of pointers to different types that differ only on signed-ness, so it does provide guarantees regarding that particular conversion, although it does not allow you to do a `static_cast`. Also note that `static_cast` allows for many unsafe conversions. I tend to see this more from the point of view of the developer. A cast is a warning sign. A `reinterpret_cast` is a warning sign with strobing lights. – David Rodríguez - dribeas Jul 10 '13 at 21:12
  • 1
    Well, then the whole thing's just arbitrary! – Dave Lillethun Jul 11 '13 at 00:14