C++ When should we prefer to use a two chained static_cast over reinterpret_cast
Asked Answered
M

6

8

First of all, this is not a duplicate of Why do we have reinterpret_cast in C++ when two chained static_cast can do it's job?.

I know situations where we cannot use even two chained static_cast to achieve that, what reinterpret_cast does. But is there any situation where I should prefer a two chained static_cast over a simple and more readable reinterpret_cast?

Meltwater answered 6/7, 2011 at 9:44 Comment(2)
reinterpret_cast for me is like a signal of bad design (or that i do smthing wrong). my advice : dont use reinterpret_castHazelhazelnut
@den bardadym - I've written very much low level code for hardware devices in the past ... I hadn't could have this done, without using reinterpret_cast. I know what you mean and in most (except low level) cases it might be true, but not in general.Meltwater
E
8

reinterpret_cast should be a huge flashing symbol that says THIS LOOKS CRAZY BUT I KNOW WHAT I'M DOING. Don't use it just out of laziness.

reinterpret_cast means "treat these bits as ..." Chained static casts are not the same because they may modify their targets according to the inheritence lattice.

struct A {
    int x;
};

struct B {
    int y;
};

struct C : A, B {
    int z;
};

C c;
A * a = &c;

int main () {
    assert (reinterpret_cast <B *> (a) != static_cast <B *> (static_cast <C *> (a)));
}

If you are not 100% sure that a points to a b, use dynamic_cast which will search for the above solution (albeit with a runtime cost). Bear in mind that this may return NULL or throw on failure.

I'm trying to think of times when I've actually used reinterpret_cast, there are really only two:

  • when a function is zipping/encrypting an arbitrary buffer and I want to use a const char * to traverse it
  • if(*reinterpret_cast<uint32_t*>(array_of_4_bytes_A) < *reinterpret_cast<uint32_t*>(array_of_4_bytes_B) or somesuch. Lines like this invite scrutiny and demand comments.

Otherwise if you have a A* which is really a B* then you probably want a union.

Erwin answered 6/7, 2011 at 9:50 Comment(4)
Note that the code with two static casts via void* is just as crazy as the reinterpret_cast, but you no longer have the huge flashing symbol. If you think it's crazy, you should leave the crazy-warning. If you think it isn't crazy, you should remove the crazy-warning.Delsiedelsman
Crazier. That big flashing crazy warning is important. Don't hide it.Haggle
@Erwin - Of course, you shouldn't use reinterpret_cast with pointers to objects, unless you're absolutely sure that the current type pointer-to-A is actually of type pointer-to-C. However, it's still bad design.Meltwater
Before unrestricted unions in C++0x, there were a few legitimate cases for this (typically involving placement new also) but you had to be careful. www2.research.att.com/~bs/C++0xFAQ.html#unionsErwin
H
4

I for one would rather see reinterpret_cast <TargetType> (pointer_of_some_other_type) than static_cast <TargetType> (static_cast <void*> (pointer_of_some_other_type)) or static_cast <TargetType> ((void*) (pointer_of_some_other_type)) any time. That chain of casts going through void* is just a sneaky, underhanded way to avoid using the dreaded reinterpret_cast.

Many projects ban the use of reinterpret_cast unless a waiver is granted; the person who wrote the code needs to justify the use of the cast. IMO, a chain of static casts is worse (much worse!) than is reinterpret_cast. The chain has the same effects, the same problems as does a reinterpret_cast, but the chain does not have the benefit of being easy to find with a grep.

Addendum
Look at it this way. Case 1, you use reinterpret_cast, you go through all the project hoops to justify its use, the project manager grants a waiver. Months later, an error is traced to your use of dynamic_cast. You have a get out of jail free card. It is the project manager's ass that is on the line for giving you that card.

Case 2, you use the sneaky, underhanded chain of static casts and the code sneaks through peer review unscathed. Months later, an error is traced to your use of despicable technique. Your project manager might be in a bit of trouble for not catching this nastiness, but it is your ass that is on the line. You do not have that get out of jail free card. You do not pass Go. You go directly to the unemployment line.

Haggle answered 6/7, 2011 at 10:27 Comment(7)
"much worse" - although for the rare cases that the result of the two static casts is strict-alias-safe, and well-defined, there's a flaw in the C++03 standard that means the result of the reinterpret_cast is unspecified. Which is a shame, but as Alf says it's not something you worry about in practice. So you can decide (a) in our style, is reinterpret_cast a big flashy sign, and if so (b) is a big flashy sign appropriate for converting a pointer-to-POD into a char* at the first byte? If "yes, no": use the static casts or at the very least have a shortcut to get the waiver automatically.Delsiedelsman
For instance, one way to avoid jumping through hoops needlessly might be to use boost:is_pod to write your own cast-style template function that can do the conversion deemed safe. Whether that template uses reinterpret_cast or static_cast then is irrelevant, since it's one carefully-constructed use, and nobody else in the company has to write either cast, they can use the function. TBH, a static_cast from void* should be suspect anyway, if I'm going to sack the programmer for that then in good conscience I have to sack the PM and code reviewer too.Delsiedelsman
Your idea of a cast-style template is a nice workaround. That template can undergo deep scrutiny to ensure that it works right. In my world, programming standards typically are not hard and fast rules. Waivers can be granted (sometimes), sometimes easily, sometimes not, sometimes 15 months after the project ends. mutable is a forbidden keyword, for example. Getting a waiver for declaring a mutex to be mutable is a piece of cake. Volatile is also a forbidden keyword. Getting a waiver for using volatile is, well, a bit more volatile and dicey.Haggle
"sometimes 15 months after the project ends" - not as bad as I feared then. If you're allowed to do it (as the best you can manage right now) but leave it marked as a known issue for future confirmation or change, then that's a lot better than the code being held up in initial review until all stakeholders have been consulted. Once the code is in and available for testing, those stakeholders can decide for themselves whether they're able to make time to look at it before release :-)Delsiedelsman
@Steve Jesspo - How would one declare such a template. I don't think it's possible to suit every situation.Meltwater
@FrEEzE2046: well, the basic idea is template <typename T> char* get_chars(T *p) { return reinterpret_cast<char*>(p); }. But first you have to handle cv-qualification of type T correctly, and then you have to limit what types T are allowed as the template parameter. This will be through some combination of specializations, type traits, SFINAE, compile-time assertions. As David suggests, it needs scrutiny to ensure that the template only instantiates for types that it's safe with. In fact you could allow any object type without seeing problems in practice on any implementation I've used.Delsiedelsman
Another useful tool, although I don't know at the moment if/how it could be implemented, would be something called is_alias_safe<T,U>, which tells you whether the two types T and U can be aliased. That would let you extend get_chars to something that can try to cast to any type, although won't always work. It'd be easy to handle the builtin types just by listing the legal type-puns. Then the template would replace the type-pun warnings that GCC gives you.Delsiedelsman
G
2

Always use reinterpret cast as a last resort - it doesn't do any checking! - so if you can chain two, three or ten statements together that do some kind of validation on the operation then you've gained something worthwhile.

Glomerate answered 6/7, 2011 at 9:49 Comment(0)
I
2

Since, the listing of the scenarios can go very long, I am putting in simple words:

If chained static_cast<>s don't give compile-error, you should avoid reinterpret_cast<>.

Isle answered 6/7, 2011 at 9:49 Comment(5)
shouting doesn't make it more right. it's just a subtle forgot-to-include-that defect in the current standard, already fixed. andrei and sutter wrote guideline based on that omission, but since things are already fixed and since no compiler does things wrong following that guideline would be as silly as pretending a non-contiguous string buffer, or whatever based on purely formal problems in the pastSelfless
@Alf P. Steinbach, the shouting was not intended. :)Isle
I strongly disagree. Chained static casts that go through void* are far, far worse than reinterpret_cast. Give your project manager that huge flashing symbol. The manager and the code reviewers will see it and may have suggestions for how to avoid it. The chained static casts are hiding that big flashing sign when it should be there for all to see.Haggle
@David Hammen - I absolutely agree with you. Technically, reinterpret_casts and chained static_casts are the same. The only "benefit" you achieve is hiding what you're actually doing.Meltwater
Your emphasised line is correct only if you don't go through void*Erwin
E
1

You should not use reinterpret_cast in cases where cross-cast pointers - instead use implicit conversion to void*, then static_cast.

Enrika answered 6/7, 2011 at 9:49 Comment(3)
Why is an implicit cast here a good choice? Afaik using implicit casts is as big a no go as using reinterpret_cast, but maybe you have good reasons for using it with void*?Untaught
@KillianDS: I changed the link - please see the linked question.Enrika
Uh, the discussion you link to is mostly mumbo jumbo. Looks formal but ain't. Don't propagate these urban myths, please_: they add tons of work with no advantage.Selfless
H
0

Because in theory, they could do something different (although it's hard to imagine such a case). More importantly, they send different signals to the reader, and tell a different story to the compiler (which could affect optimization). Logically, I'd say use chained static_cast through void* for cases to/from a character type (e.g. dumping an image of the raw memory), and other cases which are well defined and portable, and reinterpret_cast when you're doing real low level, hardware dependent work: extracting the exponent field of a float by casting its address to an unsigned int*, and bit masking, for example.

Hazlitt answered 6/7, 2011 at 10:32 Comment(7)
Please a give an example of "they could do something different" (that's not just an example of the C++98 missing support for void* reinterpret_cast)?Selfless
Personally, I would much rather have any conversion from void* to some other type be done through reinterpret_cast than static_cast. Project managers rightfully want to know when such goofiness is taking place. In fact, this may well become a rule on the projects over which I have some say on the coding standards. (That C++98 did not support this is so last millennium.)Haggle
@Alf what sort of example. The double static_cast has semantics defined by the standard, and is guaranteed to work (in the specific case mentioned). What reinterpret_cast does is implementation defined, and implementations have occasionally been known to be perverse. (Practically, I'll occasionally use reinterpret_cast in such cases, despite the inappropriate red flag it raises, because in practice, it works, even if in theory, it might not.)Hazlitt
@James Kanze - 5.2.10.[7] - reinterpret_cast "A pointer to an object can be explicitly converted to a pointer to a different object type. When a prvalue v of type “pointer to T1” is converted to the type “pointer to cv T2”, the result is static_cast<cv T2*>(static_cast<cv void*>(v)) if both T1 and T2 are standard-layout types (3.9) and the alignment requirements of T2 are no stricter than those of T1."Meltwater
@FrEEzE2046 In my copy, 5.2.10/7 reads "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." You must be looking a future version of the standard.Hazlitt
@James Kanze - I'm sorry, you're right. I've quoted from the newest C++0x draft.Meltwater
"The double static_cast has semantics defined by the standard, and is guaranteed to work (in the specific case mentioned)." Where is that guarantee written?Saline

© 2022 - 2024 — McMap. All rights reserved.