Downcasting using the 'static_cast' in C++
Using static_cast
to cast an object to a type it doesn't actually have yields undefined behavior. The symptoms of UB vary widely. There's nothing that says UB can't allow the derived member function to be called successfully (but there's nothing that guarantees that it will, so don't count on it).
Here is the rule for downcasting using static_cast
, found in section 5.2.9 ([expr.static.cast]
) of the C++ standard (C++0x wording):
A prvalue of type "pointer to cv1
B
", whereB
is a class type, can be converted to a prvalue of type "pointer to cv2D
", whereD
is a class derived fromB
, if a valid standard conversion from "pointer toD
" to "pointer toB
" exists, cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, andB
is neither a virtual base class ofD
nor a base class of a virtual base class ofD
. The null pointer value is converted to the null pointer value of the destination type. If the prvalue of type "pointer to cv1B
" points
to aB
that is actually a subobject of an object of typeD
, the resulting pointer points to the enclosing object
of typeD
. Otherwise, the result of the cast is undefined.
Can I use static_cast to down casting?
Some sites say...
Don't mind what some sites say. The standard (working draft) says:
An lvalue of type “cv1 B”, where B is a class type, can be cast to type “reference to cv2 D”, where D is a class derived from B, if cv2 is the same cv-qualification as, or greater cv-qualification than, cv1.
Also, it contains an example that is almost the same you are asking for:
struct B { };
struct D : public B { };
D d;
B &br = d;
static_cast<D&>(br); // produces lvalue to the original d object
Therefore I would say that yes, you can do that.
There are plenty of uses for such a cast. As an example, CRTP idiom is a case where you know exactly what's the type of the derived class and you don't want to check if the cast is valid through the use of dynamic_cast
. Your case is probably another one of those (it's hard to say without the original code).
Why does static_cast allow downcasts when logically it should refuse them for safety purposes or static_cast is not about safety?
I thought
static_cast
was all about safety (that C style cast were unable to provide)
static_cast
is safer than a C-style cast. But not because it's impossible to go wrong with it. It's safer because it's only less likely to go wrong. When we write a C-style cast, a compiler will go through this list to appease us:
When the C-style cast expression is encountered, the compiler attempts
to interpret it as the following cast expressions, in this order:
const_cast<new_type>(expression)
;static_cast<new_type>(expression)
, with extensions: pointer or reference to a derived class is additionally allowed to be cast to pointer or reference to unambiguous base class (and vice versa) even if the base class is inaccessible (that is, this cast ignores the private inheritance specifier). Same applies to casting pointer to member to pointer to member of unambiguous non-virtual base;static_cast
(with extensions) followed byconst_cast
;reinterpret_cast<new_type>(expression)
;reinterpret_cast
followed byconst_cast
.
The first choice that satisfies the requirements of the respective
cast operator is selected, even if it cannot be compiled (see
example). If the cast can be interpreted in more than one way as
static_cast
followed by aconst_cast
, it cannot be compiled. In
addition, C-style cast notation is allowed to cast from, to, and
between pointers to incomplete class type. If both expression and
new_type are pointers to incomplete class types, it's unspecified
whetherstatic_cast
orreinterpret_cast
gets selected.
The point in favoring static_cast
to that, is that you have a finer grained control over the resulting cast, which does grant a measure of added safety. But it doesn't change the fact that the C++ object model requires that static_cast
support casting even when undefined behavior is possible. Only dynamic_cast
(not on the above list, by the way) has an added bit of safety for polymorphic types, but that's not without overhead.
C++ static_cast downcast validity
static_cast
for performing downcast does not perform any safety checks. Since it's possible for a Base&
to be referencing an instance of A
, the cast proceeds and since it's NOT actually referencing an A
, we enter undefined behavior territory*.
A dynamic_cast
on the other hand is safer. It will perform a check and throw an exception in the case of reference casting, or return nullptr
in the case of pointer casting†.
However, since your base class lacks any virtual functions, dynamic_cast
is impossible, so you'd need to modify it with at least a virtual destructor:
class Base{
public:
int m_object;
virtual ~Base()=default;
};
Now if we tried to cast:
A* a = dynamic_cast<A*>(&base);
if (a)
a->arggh();
else
std::cout << "null\n";
Our output would be
null
*Relevant standardese can be found in [expr.static.cast]:
[for a cast like
static_cast<D&>(b)
whereb
is an instance of a base class forD
], If the object of type “cv1B
” is actually a base class subobject of an object of typeD
, the result refers to the enclosing object of typeD
. Otherwise, the behavior is undefined.
†Relevant standardese at [expr.dynamic.cast]
The value of a failed cast to pointer type is the null pointer value of the required result type. A failed cast to reference type throws an exception of a type that would match a handler of type
std::bad_cast
downcasting using static_cast - pointers and objects
In the first one, you are saying "I have a pointer, and I promise it's pointing at a der
, so please just go with it".1
In the second one, you can make no such promise, because you unambiguously have a base
, not a der
.
1. Of course, because it doesn't actually point at a
der
, you'll get undefined behaviour at runtime. Why is this value downcast (static_cast to object type) allowed in C++20?
It is legal in C++20.
[expr.static.cast]/4:
An expression
E
can be explicitly converted to a typeT
[...] ifT
is an aggregate type ([dcl.init.aggr]) having a first elementx
and there is an implicit conversion sequence fromE
to the type ofx
.
[dcl.init.aggr]/2:
The elements of an aggregate are: [...] for a class, the direct base classes in declaration order, followed by the direct non-static data members ([class.mem]) that are not members of an anonymous union, in declaration order.
So Derived
is an aggregate whose first element is Base
, and since C++20, it is permitted to create an aggregate from its first element.
This feature is introduced in P1975R0 "Fixing the wording of parenthesized aggregate-initialization".
static_cast dangling reference when downcasting
It is undefined behaviour, but not because of a dangling reference. The act of casting an object using static_cast
from a parent class to the child class when it is not actually that child is undefined behaviour:
B& b = f(a); // Casts `A` type to `B` incorrectly
See the relevant standard quote here.
Example of useful downcast with static_cast which does not produce undefined behaviour
A very basic example:
class Animal {};
class Dog : public Animal {};
void doSomething() {
Animal *a = new Dog();
Dog *d = static_cast<Dog*>(a);
}
A more contrived example:
class A { public: int a; };
class B : public A {};
class C : public A {};
class D : public B, public C {};
void doSomething() {
D *d = new D();
int bValue = static_cast<B*>(d)->a;
int cValue = static_cast<C*>(d)->a;
// This does not work because we have two a members in D!
// int eitherValue = d->a;
}
There are also countless other cases where you know the actual type type of the instance due to some flags or invariants in your program. However, many sources recommend preferring dynamic_cast
in all cases to avoid errors.
Is it undefined behavior to static_cast down a type that isn't actually the type of the object?
cppreference is written in English with the intent to convey good understanding, it's not actually specification. But I have no problem with its wording here:
Such
static_cast
makes no runtime checks to ensure that the object's runtime type is actually D, and may only be used safely if this precondition is guaranteed by other means.
If you guarantee the precondition, it's fine. If you don't guarantee the precondition, then your code isn't safe. What does it mean for code to not be safe? Its behavior isn't defined.
The actual specification uses this wording:
If the prvalue of type “pointer to cv1
B
” points to aB
that is actually a subobject of an object of typeD
, the resulting pointer points to the enclosing object of typeD
. Otherwise, the behavior is undefined.
Either way, in your example, all of your casts are valid.
B*pb=static_cast<B*>(pa);//downcast,**is it undefined or just not safe?**
You're missing the condition. A downcast isn't undefined behavior, in of itself. It's only undefined behavior if there isn't actually an object of derived type there. And in this case pa
is pointing to a C
, which is a B
, so the cast to a B
is safe.
This, however, is not:
struct B { };
struct D1 : B { };
struct D2 : B { };
B* p = new D1;
static_cast<D2*>(p); // undefined behavior, no D2 object here
Related Topics
How to Create Directory Tree in C++/Linux
Why Are References Not Reseatable in C++
How to Find the Index of Current Object in Range-Based For Loop
A Std::Map That Keep Track of the Order of Insertion
What Happens in a Double Delete
Is There Any Real Risk to Deriving from the C++ Stl Containers
What Is the Meaning of the Term "Free Function" in C++
What Are the Correct Link Options to Use Std::Thread in Gcc Under Linux
Pre-2016 Valgrind: Memory Still Reachable With Trivial Program Using ≪Iostream≫
How to Initialize Base Class Member Variables in Derived Class Constructor
Unresolved External Symbol _Imp_Fprintf and _Imp_Iob_Func, Sdl2
How to Parse Space-Separated Floats in C++ Quickly
How to Pass a Member Function Pointer
Why Does This Function Push Rax to the Stack as the First Operation
Visual Studio Code, #Include ≪Stdio.H≫ Saying "Add Include Path to Settings"