On a different issue, C-style casts are even nastier than you've described. Here's two more nasty things they do.
First, they are even more desperate than a reinterpret_cast. A reinterpret_cast at least promises to preserve constness on pointer conversions. (You can lose constness by converting to an integer and back, but at least that requires two reinterpret_casts.) But a C-style cast doesn't do that; it will do the work of both a reinterpret_cast and a const_cast.
But it gets even more desperate! It ignores access specifiers in order to achieve its goals. Imagine you have this:
class Derived : public NonEmptyBase, private Base {};
Note the private inheritance. You cannot implicitly cast a Derived* to a Base*, nor can you static_cast a Base* to a Derived*. You can reinterpret_cast them, but that's a bitpattern cast, not a hierarchy cast.
But a C-style cast will do the hierarchy cast. Yes, that weird thing will actually ignore the fact that the relationship between Derived and Base ought to be invisible, and will do a hierarchy cast instead of falling back to a reinterpret_cast. I'm not sure if that's a good thing or not (at least it kinda works), but it's definitely weird.
Second, the missing hierarchy trap is worse that you described. To recap, you said that (Derived*)base_ptr is dangerous because, due to a programmer error, Derived might not actually derive from Base, but the C-style cast will happily fall back to a reinterpret_cast in this situation.
Well, Derived might actually be derived from Base, and this might *still* happen!
static_cast reverses implicit conversions, with some exceptions. It can't reverse added cv-qualifiers. It can't reverse array and function decay. It can't reverse constant 0 to null pointer conversions. And it can't reverse a number of boolean conversions.
And it can't reverse a hierarchy cast through a virtual base.
class Base {}; class Derived : public virtual Base {};
Base* pb = new Derived(); // valid
Derived* pd = static_cast<Derived*>(pb); // doesn't compile
Derived* pd2 = (Derived*)pb; // reinterprets!
The reason is that in the object layout of virtual inheritance, given a Derived*, I can find the Base subobject via either following a pointer in the Derived*, or adding an offset specified in the vtable to the this pointer. (Depending on the implementation.) But finding the Derived object in the actual object given only a Base*, I cannot do the reverse. The vtable for Base doesn't contain an offset to the Derived superobject, because Base might not have a Derived superobject. So I have to invoke the full dynamic_cast machinery anyway, just to find out where the Derived superobject lives.
Which can fail at runtime. static_cast must not fail at runtime. It must be simple and cheap. So it fails at compile time instead.
Whereas the C-style cast just moves on and does a reinterpret_cast, which is most definitely not the right thing.
This means that, if you have a C-style hierarchy cast, you can turn your program from well-defined to silently and nastily undefined simply by adding "virtual" to some class definition. If you had used static_cast, at least it would only fail to compile, and you could use a dynamic_cast there.
Now don't you wish you hadn't used the C-style cast?