58 comments

[ 2.9 ms ] story [ 119 ms ] thread
This article spends a ton of time explaining very rudimentary things and then completely glosses over what it intends to convey.

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 agree, the article tells how but not why.

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'm sure this will be controversial, but my attitude towards C++ type casting is much, much simpler.

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.

The other advantage other than greppabilty/documentation is that the compiler will limit what the static_cast does. Since a c style cast can be a reinterpret cast or a const cast you do lose some compiler provided protection.
That's true, but there's a reason I chose not to list it - this has never actually saved me from a bug, ever. Of course, as I said, your mileage may vary.
The only meaningful reason to prefer static_cast over C-style casts is easy greppability.

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.

But it's a completely worthless compiler error. The fix is always to replace static_cast with reinterpret_cast. In 15 years of C++ I can't think of a single time that this error has actually caught a legitimate mistake.
> The fix is always to replace static_cast with reinterpret_cast.

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.

I guess I had the benefit of some years of learning to smell for C aliasing violations, but even if I hadn't, static_cast would have been a dreadful way to learn. If static_cast were purely to detect pointer aliasing violations, it may have some amount of usefulness. But it also whines on totally legitimate conversions. Like both these casts:

        // 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".
The static_cast vs. reinterpret_cast is also executable documentation for your readers. Sounds like you have a strong handle on this, and it's a free opportunity to make your future maintainers' lives a little bit easier.
I appreciate that in general but not here. What does reinterpret_cast<intptr_t>(p) tell a reader that (intptr_t)p doesn't?
Since this is about maintenance, it's ultimately craftsmanship (IMHO) amd I don't think I can give a great answer without knowing real context. Here's one possibility that comes to mind:

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.

The fix is always to replace static_cast with reinterpret_cast.

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.

Now that `mutable` exists const_cast should indeed be avoided.

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.

Agreed. But triggered by a thing written there: a pointer in a jlong? Is there any guarantee these things are the same size? Works on one platform, cool, maybe that's enough. But doesn't sound portable.
jlong is always 64 bits.

I would be interested in knowing more details about platforms where your pointers are wider than 64 bits. Not impossible, but certainly pathological.

Yeah, but a void pointer isn't always 64 bits…
I’m interested in the specific cases where it isn’t (where it’s larger than 64-bits). I can’t think of an example where a simple object pointer is larger than 64 bits. Can you run the JNI on those platforms?
Complex pointers (object/method pointers) can sometimes exceed that?

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.

Sure, but if you go down chasing portability guarantees in e.g. the C++ spec, you’re going to go insane pretty quickly.
That but also Java doesn't really give you a better option here. There's no IntPtr type in Java like there is in C#.
The only point of IntPtr is to save four bytes on 32-bit platforms. Why look for a better option when jlong is damn well good enough?
> Complex pointers (object/method pointers) can sometimes exceed that?

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.

I guess I was thinking of virtual function pointer. Its larger than a regular function pointer, which is not necessarily the same size as a data pointer. But if you're dealing with object pointers only, all good!
Yeah, this is why I emphasized that these are prescriptions tailored to my personal usecases and style, not something that's supposed to work for everyone. I don't do anything remotely Java-related, so I have no opinion on your JNI example.

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.

I prefer memcpy() for those (or a custom function that calls memcpy() for ergonomics and perhaps static_asserts something about the type) rather than reinterpret_cast since it risks UB if you get it wrong.
It's pretty straightforward to grep for C-style casts, too.

The greppability advantage falls to interpret_cast. If you have bugs, the fastest way to find them is to first grep for reinterpret_cast.

static_cast from base to the wrong derived class is UB and can quickly become a vulnerability:

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.

This is probably a reasonable point in a subset (and mindset, frankly) of C++ that has very little overlap with the subset of C++ I use. Every C++ codebase I work on has RTTI (and exceptions) disabled immediately, so dynamic_cast isn't even an option. If it was, I would consider any use of dynamic_cast to be a bug because of the egregious performance implications. The correctness concern should be addressed either at compile time (either by construction, i.e. CRTP over runtime polymorphism or by other means) or by tests. Obviously, I'm extremely biased on this, because performance is effectively the primary (and often only) reason I use C++. In other domains, YMMV.
I can't think of a dynamic_cast scenario I've seen that was not indicative if an improper class design; typically that the base class doesn't have proper virtual methods defined, or there's a missing ABC interface, or the inheritance is just inappropriate.

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.

And it does not work when RTTI is absent. Had this problem in embedded development a couple days ago. Changed to static_cast, then refactored to avoid casting.
I've only used dynamic_cast in unit tests, to verify that a base class pointer is indeed of a certain subclass.
C-style casts in C++ are a sledgehammer. You will get an object of the cast type afterwards, but it may not happen the way you thought it would. That's why it's better to use the C++ casting constructs. If you try, say, a static_cast, and that's not possible, the compiler will tell you that in an error message. Then you're forced to reason through why it's not possible, and what you need to do about it.
Counterpoint… it’s a bit verbose when you need to cast for bitwise operations.

    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.
(comment deleted)
It can be useful when used for interface-style pure virtual inheritance.
(comment deleted)
What do you think of the following:

  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?

> Now, you are given an AInterface* object and want to know whether it has a certain guarantee or not.

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.

I fully agree, this is what I would go for in this case. The only caveat is that this is duplicating information that RTTI already has available, i.e. space overhead. That's not a strong argument, but still a pareto-optimal niche.
Given most people use C++ for its performance in time rather than in space, I think most projects would go with a member/virtual method.
I would argue that much like Go's aversion to deep inheritance hierarchies, this interface hierarchy would be annoying to use in practice.

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.

> this interface hierarchy would be annoying to use in practice

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.

In C++, you can use templates to pattern match/duck type interfaces at compile time.

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.

It actually works really well in practice for the example I'm thinking of (and this is deep in the bones of a code base, not just something I thought of for fun). Each functionality is a true subset of the other. It's not different families of functionalities.

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.

"template<typename ABCType> foo(const ABCType& someObject)" will call the most optimized code path at compile time if the type is properly cast before the call.

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.

...and every single piece of code in your entire code base that touches these classes (and that's like 80% of the higher level code in this particular case) would have to be templated. So either everything has to be in headers, or you need 3 (well, 5 in reality, I abbreviated the example) explicit template instantiation declarations per function in the header and as many explicit template instantiation definitions in the source file. Either way lies madness.
I use c style casts everywhere and I've stopped using every other style. Why? Because my projects are my own and I know what everything does and what everything should be. I used the other style in a couple c++ based projects and then never again. Wouldn't touch them unless a team made me.
I know what everything does and what everything should be

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.

Well, a C-style cast of a pointer to an instance of the derived class to the pointer to the base may not work in case of multiple inheritance, because it is equivalent to reinterpret_cast which effectively does nothing, whereas static_cast will do the right thing.
I default to blacklisting all js and I feel like this website is a great example of an utterly unnecessary usage of front end scripting and the no-js experience is... well, just a blank screen with a wobbly needle.
I have a slow connection and I noticed the text is already loaded; I just went into reader mode and it bypassed the loader.
It has a jarring and useless full-screen white curtain up animation.
There's another, not in the C++ language but in the C++ standard library, called bit_cast: https://en.cppreference.com/w/cpp/numeric/bit_cast

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.

I suggested an underlying_cast function that converts a typed-enum to its underlying type. It's quite useful for things like sending typed enums to streams.
That's just static_cast<std::underlying_type_t<EnumType>>(value), right?
Yes, pretty much. But everyone will probably end up writing this at some point. It may as well be in the standard library.
This might be off topic, and certainly unimportant, but I never liked underscores in language keywords. For example if your C++ style guide calls for camelCase identifiers these look jarring. Any reason why the designer didn't just introduce three keywords "cast" "dynamic" "reinterpret" instead? Note that "static" and "const" are already keywords.