C++ type casting with example for C developers
vishalchovatiya.com
vishalchovatiya.com
It's also brutally slow in most compilers.
After that consideration, C-casts are mostly fine. static_cast/reinterpret_cast are more explicit but the syntax is bizarrely verbose.
But I'm way more annoyed by dynamic_cast than C-casts whenever it they come up.
uint32_t packed = ((uint32_t)x << 16)
| ((uint32_t)y & 0xffff);
uint32_t packed = (static_cast<uint32_t>(x) << 16)
| (static_cast<uint32_t>(y) & 0xffff);
The verbosity of static_cast can get out of hand, and make it harder to read. class AInterface { /*...*/ };
class BInterface : public AInterface { /*...*/ };
class CInterface : public BInterface { /*...*/ };
class AImpl : public AInterface { /*...*/ };
class BImpl : public BInterface { /*...*/ };
class CImpl : public CInterface { /*...*/ };
In this use case, assume going from AInterface to CInterface implies stronger and stronger guarantees that the implementations provide. Also assume LSP as satisfied.Now, you are given an AInterface* object and want to know whether it has a certain guarantee or not. A dynamic cast answers this with minimal space overhead.
Yes, there is certainly run time overhead in the dynamic cast, and you could just have a virtual method that returns some encoding of the guarantees (e.g. as an enum), but the dynamic_cast is still pareto-optimal in terms of space.
Change my mind?
Eiffel guarantees this in the LSP sense, by enforcing that overrides `require` as much or less than their base class, and `ensure` as much or more than their base class.
If you want to query that from AInterface alone, you should expose a virtual member that allows you to make the specific inquiry of interest from AInterface. In effect, if users of AInterface must know that only some AInterface's meet some guarantee, you must expose some knowledge of the guarantee in AInterface itself.
For example, AInterface can expose a boolean predicates like supports_foo, or it can return an object that is comparable with a std::strong_ordering.
If A, B, and C are different families of functionality then ditch the inheritance and use separate interfaces for each family of functionality.
The cascading dynamic_cast logic required is a code smell that this could be made simpler.
No doubt, but it happens, and sometimes because the refactoring you might prefer to do can't be done or can't be done right away.
For example, in Java you might have a subclass of Principal where you store additional metadata and have additional methods, then you might have code like:
public static Something princOk(Principal p, Stuff stuff)
throws MyPrincipalException
{
if (p instanceof MyPrincipal)
return princOk((MyPrincipal)p, stuff);
return princOk(MyPrincipal(p), stuff);
}
Why would you do this? Well... because many JDK and third-party interfaces take Principal object arguments that you could reuse... if only you could decorate those objects with the additional metadata that you wanted, and... with subclassing you get to.In Java you might say "use Subject", but it's not that easy, and anyways, JAAS is a terrible terrible thing that should have been ripped out when applets were ripped out.
So "template<typename PrincType> void princOk(PrincType& p, Stuff& stuff) {p.doThing(stuff); }" would map properly as long as doThing was implemented in all the different classes. You don't actually need an interface or base class for it to work.
The rest of the dynamic mapping is faster by calling switch() on an enum class member and than static_cast rather than relying on dynamic_cast.
Also, I don't see how your suggestion would help with
void foo(const AInterface& someObject);
What type should foo take instead if it can handle all levels of guarantee but might engage better code paths the more guarantees are available?Would you use a sum type (which in C++ boils down to type erasure, i.e. polymorphy but hidden and costlier)? Would you overload it (and then have code duplication)? I'm not convinced you could make it simpler.
In this case, the inheritance is unnecessary as long as all the types implement the same function names. So the runtime type detection, whatever it is, only needs to be run once at the start of the callstack and then an entire chain of operations will be more optimized than if you had used interfaces and virtual functions throughout.
It essentially just calls memcpy after some basic checking. It's similar to reinterpret_cast in spirit but more powerful. You can convert for example a float array into a char array (I don't think any of the four standard casts will allow you to convert a whole array at a time). Compilers like clang will generally recognize memcpy as __builtin_memcpy which sometimes generates no instructions at all.
1. I only use static_cast, the other three C++-specific casts are, in my opinion, somewhere between useless and harmful.
2. The only meaningful reason to prefer static_cast over C-style casts is easy greppability. If you find you don't benefit from this difference, C-style casts are just fine.
These are obviously highly opinionated personal rules that serve me well in the subset of C++ I prefer to use. Your mileage can and very likely will vary substantially.
That's not just it: static_cast will also result in a compiler error when trying to cast things which don't make sense so that's extra safety.
That sounds like a fast trip to aliasing violations (aka UB). There's no way you only ever casted from/to byte arrays or void* when you applied the above fix over 15 years.
// float *x = ...
static_cast<int*>(x)
static_cast<intptr_t>(x)
…give the exact same compiler error. And in the case of the first one, which is actually bad, the compiler can't explain why it's dangerous. Thus it doesn't actually tell me "hey you probably are casting to the wrong thing", but rather "you picked the wrong cast to make the compiler happy".Places where I would _expect_ to see something like this are C APIs where void pointers are used to provide "userdata context", such as pthread_create. Using the reinterpret-cast-to-pointer-sized-thingie(void* or intptr_t or uintptr_t) makes it clear that we're discarding all type safety and probably doing something similar. The C style cast does not express that as clearly.
Is the impact substantial? Not really. But in maintenance of long lived code bases, small impacts accumulate.
I've had cases when I was refactoring and suddenly somewehere in template code the compiler bascially told me that what I was trying to do was plain wrong, i.e. casting between completely unrelated types. In which case the fix is rewrite, not resort to reinterpret_cast.
But `reinterpret_cast` has quite a bit of use, particularly in FFI scenarios. For example when using JNI there is no void* type in Java, so you are forced to store the pointer in a jlong. But you also need to be super duper sure that no conversion happens, it's _just_ a really tiny 8-byte allocation. So you reinterpret_cast back & forth. My JNI code is littered with variants of reinterpret_cast<jlong>( T* ) & reinterpret_cast< T* >(jlong) - and it is neither useless nor harmful.
reinterpret_cast is also quite useful when writing custom allocators. More niche usage there, of course, but still far from useless or harmful.
> 2. The only meaningful reason to prefer static_cast over C-style casts is easy greppability. If you find you don't benefit from this difference, C-style casts are just fine.
If you do actually think that static_cast is the only useful cast then this is super wrong advice. C-style casts are "compiler guesses if it's static_cast or reinterpret_cast, and const-ness is completely ignored." They are not a shortened alias for static_cast.
I would be interested in knowing more details about platforms where your pointers are wider than 64 bits. Not impossible, but certainly pathological.
Anyway, I see the issue, you're stuck having to do something and that's a pretty good solution. Maybe add an assert somewhere to the effect that sizeof(jlong) >= sizeof(pointer) which is free on your platform.
They can't. void* / intptr_t is a fixed-size, and at least on all platforms I run on that size is always 32-bit or 64-bit. Both of which fit perfectly fine in the always-64-bit jlong.
If you're thinking of std::mem_fn or similar note that they are defined to return an undefined object not a pointer. So of course those can be larger than the size of a pointer, since they aren't a pointer.
But sure toss in a static_assert(sizeof(jlong) >= sizeof(void*)) if you want.
On the latter point - yes, C-style casts are a broader concept than static_cast. In practice, as I said in another comment, the stricter constraints of static_cast have never prevented me from making a bug, so I don't view that as an advantage that would justify the extra verbosity. I do write custom allocators a lot and mostly find myself just using C-style casts there - that code already tends to be almost entirely C anyway and the logic of the casts tends to be extremely obvious from the context. If you really care about greppability to a religious extent, I suppose this is an argument for reinterpret_cast - I just don't find it convincing, personally.
The greppability advantage falls to interpret_cast. If you have bugs, the fastest way to find them is to first grep for reinterpret_cast.
https://wenke.gtisc.gatech.edu/papers/caver.pdf
dynamic_cast to the wrong derived class has defined behavior and is imo worth relying on when speed is simply irrelevant (e.g. once per button click). That still leaves the argument open whether casting from base to derived is ever good, see other comment trees in this thread.
C-style casts aren't evil, but they are powerful. They can also be used in places where more than one C++ cast would be required. C developers are very likely to use the C style because it mostly gets the job done the first try.
C developers working on C++ code are not likely to be swayed much by anything that doesn't make a strong case for having 4 different cast operators and this article doesn't manage to do that.
I think there are two main reasons to prefer the C++ style casts.
1. The C style cast does too much. Some of the conversion it does are pretty safe, some are dangerous. The C++ casts try to differentiate the relatively safe casts from the dangerous casts. Any code with reinterpret_cast is worth taking extra time to scrutinize.
2. The second is that C style casts are hard to search for (Type). By giving names to these operations, you can more quickly search code for problematic areas.
3. Incorrect aliasing is a big deal. In the C and C++ standards it is undefined behavior, which means you lose the ability to reason about your program. Making the operations that can trigger this stand out is a good thing.
I used to be like that, but turns out after 5 years not looking at a piece of code you don't know anything anymore (well at least I don't) and then change something only to have something else blow up because, hey, C-style casts ftw right, less typing and less caring.
The other casts at least offer some protection against that at compile-time. There's already enough loopholes in C++ that I'd rather have the least worries possible so now I just do the right thing and use the right cast. Heck I might even add a bunch of static_asserts for casts of which I know they'll only be ok if both types are the same size and layout for instance. Plus at the same time they serve as documentation of what I meant when writing he cast. Saved me a bunch of times already.