static_cast safety
In short, because of multiple inheritance.
In long:
#include <iostream>
struct A { int a; };
struct B { int b; };
struct C : A, B { int c; };
int main() {
C c;
std::cout << "C is at : " << (void*)(&c) << "\n";
std::cout << "B is at : " << (void*)static_cast<B*>(&c) << "\n";
std::cout << "A is at : " << (void*)static_cast<A*>(&c) << "\n";
}
Output:
C is at : 0x22ccd0
B is at : 0x22ccd4
A is at : 0x22ccd0
Note that in order to convert correctly to B*, static_cast has to change the pointer value. If the compiler didn't have the class definition for C, then it wouldn't know that B was a base class, and it certainly wouldn't know what offset to apply.
But in that situation where no definition is visible, static_cast doesn't behave like reinterpret_cast, it's forbidden:
struct D;
struct E;
int main() {
E *p1 = 0;
D *p2 = static_cast<D*>(p1); // doesn't compile
D *p3 = reinterpret_cast<D*>(p1); // compiles, but isn't very useful
}
A plain C-style cast, (B*)(&c)
does what you say: if the definition of struct C is visible, showing that B is a base class, then it's the same as a static_cast. If the types are only forward-declared, then it's the same as a reinterpret_cast. This is because it's designed to be compatible with C, meaning that it has to do what C does in cases which are possible in C.
static_cast always knows what to do for built-in types, that's really what built-in means. It can convert int to float, and so on. So that's why it's always safe for numeric types, but it can't convert pointers unless (a) it knows what they point to, and (b) there is the right kind of relationship between the pointed-to types. Hence it can convert int
to float
, but not int*
to float*
.
As AndreyT says, there is a way that you can use static_cast
unsafely, and the compiler probably won't save you, because the code is legal:
A a;
C *cp = static_cast<C*>(&a); // compiles, undefined behaviour
One of the things static_cast
can do is "downcast" a pointer to a derived class (in this case, C is a derived class of A). But if the referand is not actually of the derived class, you're doomed. A dynamic_cast
would perform a check at runtime, but for my example class C you can't use a dynamic_cast
, because A has no virtual functions.
You can similarly do unsafe things with static_cast
to and from void*
.
No, your "AFAIK" is incorrect. static_cast
never behaves as reinterpret_cast
(except, maybe when you convert to void *
, although this conversion is not normally supposed to be carried out by reinterpret_cast
).
Firstly, when static_cast
is used for pointer or reference conversions, the specification of the static_cast
explicitly requires a certain relationship to exist between the types (and be known to static_cast
). For class types, they shall be related by inheritance, as perceived by static_cast
. It is not possible to satisfy that requirement without having both types completely defined by the point of static_cast
. So, if the definition(s) is(are) not visible at the point of static_cast
, the code simply will not compile.
To illustrate the above with examples: static_cast
can be used [redundantly] to perform object pointer upcasts. The code
Derived *derived = /* whatever */;
Base *base = static_cast<Base *>(derived);
is only compilable when the following code is compilable
Base *base(derived);
and for this to compile the definition of both types have to be visible.
Also, static_cast
can be used to perform object pointer downcasts. The code
Base *base = /* whatever */;
Derived *derived = static_cast<Derived *>(base);
is only compilable when the following code is compilable
Base *base(derived); // reverse direction
and, again, for this to compile the definition of both types have to be visible.
So, you simply won't be able to use static_cast
with undefined types. If your compiler allows that, it is a bug in your compiler.
static_cast
can be unsafe for pointers/references for a completely different reason. static_cast
can perform hierarchical downcasts for object pointer/reference types without checking the actual dynamic type of the object. static_cast
can also perform hierarchical upcasts for method pointer types. Using the results of these unchecked casts can lead to undefined behavior, if done without caution.
Secondly, when static_cast
is used with arithmetic types, the semantics is totally different and
has nothing to do with the above. It just performs arithmetic type conversions. They are always perfectly safe (aside form the range issues), as long as they fit your intent. In fact, it might be a good programming style to avoid static_cast
for arithmetic conversions and use old C-style casts instead, just to provide a clear differentiation in the source code between always-safe arithmetic casts and potentially-unsafe hierarchical pointer/reference casts.