casts
In many languages, the syntax to convert an object is (T) expression
or T(expression)
. Both are also valid in C++ (for backwards compatibility), but discouraged.
C++ has 4 keywords dedicated for casts, each with distinct behavior. In C++11 they have been modernized. Why all of these? There are multiple reasons:
C++ is a complex language that can work on multiple levels of abstraction. There are situations where multiple ways of converting an object are possible so a single way of writing is not enough.
Cast keywords are very clear about their intent.
Cast keywords produce better compiler errors because each has narrowed set of rules.
It's easy to make a mistake with old casts, including ones that invoke undefined behavior.
One of old casts has a grammar problem - it accepts only simple-type-specifier or a typedef-specifier (less technically: a single-word type name), for example
long long(expression)
andint*(expression)
are not valid.
Old casts
Casts inherited from C, they take the form
(T) expression
.functional cast expressions (not present in C), they take the form
T(expression)
. Depending on the type, the same syntax can also mean a constructor call.
Both of these work the same (apart from grammar issues which block some usages). Put shortly, they will pick behavior identical to one of keyword casts. The picking is quite complex and there is an article describing a case where included files can affect behavior. You certainly do not want to experience undefined behavior just because of changes in some (potentially transitively) included header files.
So ... simply don't use these casts in C++11 (and later) code. The only widely accepted old cast is explicit discard of the result: (void) expression
. It relies on the fact that anything can be converted to void type - this cast should be used purely to shut warnings about unused variables (usually unused function parameters when commenting out name is not feasible if they are used on a different build configuration - different target platform or debug/release).
Cast keywords
The cast keywords are:
static_cast<T>(expression)
dynamic_cast<T>(expression)
const_cast<T>(expression)
reinterpret_cast<T>(expression)
Apart from solving ambiguity and code clarity problems, a big advantage of these keywords is that they look exactly like function templates - new casts can be added with the same syntax. Some already exist:
C++20
std::bit_cast
- obtains value of typeT
by reinterpreting object representation. This could be thought as a short form of specific usage ofreinterpret_cast
but the bit cast guarantees no aliasing violations while the keyword alone does not. It's alsoconstexpr
.boost::lexical_cast
(string to number and number to string)boost::numeric_cast
(number to number)
static_cast
The keyword is used for all typical convertions that rely on compile time information. It's by far the most used explicit convertion keyword.
The cast does not modify the source object, it creates a new object thus the source object can be const
. Possible convertions:
all implicit convertions
enum
tobool
, integer or floating-point type and reverse of itbit-field to its underlying type
anything to
void
(usually to shut compiler warnings about unused variables)constructor calls if the destination type is a class, additional implicit convertions are allowed in such case
call to user-defined convertion operator - classes can overload
operator T
to enable such convertions, many overloadexplicit operator bool
to support code likeif (obj)
pointer/reference inheritance casts (these only create a new pointer/reference, they do not modify the pointed/referenced object)
(possibly cv-qualified)
void*
to any pointer to an object type (function pointers are not object pointers) that is not less cv-qualified
Enumerations
An integer or floating point may be converted to enum
even if the enumeration has no such value. It will not be adjusted and will compare false
with any enumerator. If the enum
has no fixed underlying type, the largest allowed value is the is the largest representable value in the smallest bitfield capable of holding enumeration values.
1 2 3 4 5 6 7 8 9 |
// max value depends on range of type int enum class fixed : int { one = 1, two, three, last = 30 }; // max value = 31 (30 requires bit-field of 5 bits, which has range [0, 31]) enum class not_fixed { one = 1, two, three, last = 30 }; auto e1 = static_cast<fixed>(31); // well-defined auto e2 = static_cast<fixed>(50); // well-defined auto e3 = static_cast<not_fixed>(31); // well-defined auto e4 = static_cast<not_fixed>(50); // undefined behavior |
Type upcasts
A pointer/reference to derived class may be converted to a pointer/reference to the base class.
the cast is ill-formed if base is ambiguous (multiple inheritance can result in one type being inherited from multiple times)
the cast is ill-formed if base is inaccessible (private or protected inheritance)
if the pointer is null the resulting pointer is also null
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
struct base {}; struct derived : base {}; struct extra_derived : derived, base {}; struct privately_derived : private base {}; extra_derived ed; static_cast<derived&>(ed); // well-defined static_cast<derived*>(&ed); // well-defined static_cast<base&>(ed); // error: base is ambiguous static_cast<base*>(&ed); // error: base is ambiguous privately_derived pd; static_cast<base&>(pd); // error: base is inaccessible static_cast<base*>(&pd); // error: base is inaccessible |
Type downcasts
A pointer/reference to base class may be converted to a pointer/reference to the derived class.
The cast has same ambiguity and accessibility preconditions as the upcast, but additionally the cast is ill-formed if base is a virtual base or a base of virtual base of derived - in such case dynamic_cast
is needed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
struct base {}; struct derived : base {}; struct virtually_derived : virtual base {}; derived d; base& br = d; static_cast<derived&>(br); // well-defined static_cast<derived*>(&br); // well-defined base b; static_cast<derived&>(b); // undefined behavior static_cast<derived*>(&b); // undefined behavior static_cast<virtually_derived&>(br); // error: virtual inheritance is present static_cast<virtually_derived*>(&br); // error: virtual inheritance is present |
Because of undefined behavior risk, static_cast
downcasts must guarantee through other means that the object is in fact of derived type - usually the base class has a virtual
method which returns unique value for each type (basically manual and simplified implementation of RTTI).
Member pointer upcasts
A pointer to member of derived class can be converted to a pointer to member of the base class. The cast makes no checks to ensure the member actually exists in the runtime type of the pointed-to/referenced object.
the cast is ill-formed if base is ambiguous
the cast is ill-formed if base is inaccessible
if the pointer is null the resulting pointer is also null
1 2 3 4 5 6 7 8 9 10 |
struct base {}; struct derived : base { int m = 42; }; derived d; int derived::* d_pm = &derived::m; auto b_pm = static_cast<int base::*>(d_pm); // well-defined d.*b_pm; // well-defined base b; b.*b_pm; // undefined behavior (the runtime object has no such member) |
Member pointer downcasts
A pointer to member of base class can be converted to a pointer to member of the derived class.
the cast is ill-formed if base is ambiguous
the cast is ill-formed if base is inaccessible
the cast is ill-formed if base is a virtual base or a base of virtual base of derived class
if the pointer is null the resulting pointer is also null
More on pointers
I don't get why while pointer upcasts are implicit, pointer to member downcasts are implicit.
Pointer/reference upcasts are simple: every derived object also has a base subobject. Not so in reverse.
Pointer to member downcasts are safe because every derived class also has members of the base class. Not so in reverse.
Sidecasts
Sidecasts with static_cast
are not possible. An upcast has to be made first and then a downcast.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
struct base {}; struct left : base {}; struct right : base {}; struct derived : left, right {}; derived d; left& l = d; static_cast<right&>(l); // error: invalid cast // Valid, but actually performs reinterpret_cast. // Would perform static_cast if such was valid // (another reason not to use old casts, changes // in inheritance can cause undefined behavior). (right&) l; |
Overload sets
One very practical use of static_cast
is selecting a specific function overload to resolve an ambiguity.
1 2 3 4 5 6 7 8 |
template <typename F, typename... Args> auto invoke(F f, Args&&... args); void f(int, int); void f(double, double); invoke(f, 1, 2); // error: can not deduce F invoke(static_cast<void(&)(int, int)>(f), 1, 2); // ok |
It can be made even simpler by introducing a helper function template:
1 2 3 4 |
template <typename F> constexpr F& select_overload(F& f) noexcept { return f; } invoke(select_overload<void(int, int)>(f), 1, 2); // ok |
Why there is no
(&)
in the second example?
The first example casts the function overload set to a reference to a function of specific type (a function type would be invalid because functions can not be taken by value), the second example has the reference built into the template.
Why then the first function takes a function argument by value?
It's not a function but a function template.
Why then the first function template takes a function argument by value?
It does not because it's a function template and the template parameter is a subject to template type deduction. The deduced type may be a reference type.
Why is dedu...
Go learn templates in their dedicated tutorial!
dynamic_cast
This keyword is intended for inheritance casts.
Like static_cast
, this cast can also perform some convertions that are allowed implicitly (most notably upcast and adding cv-qualifiers).
Requirements
The result type can only be:
pointer to class type
reference to class type
(possibly cv-qualified)
void*
The input expression must be:
reference to a class type if result type is a reference
pointer to a class type if the result type is a pointer
The types involved must:
be complete (forward declarations will result in compile errors)
-
analogically to
static_cast
:there must be no ambiguity
there must be no inaccessibility
be polymorphic when performing a downcast or a sidecast (such casts perform a runtime check which involves some overhead and requires RTTI - enabled by default in most compilers)
If the cast is used on an object during construction or destruction and the result type is not a type matching the constructor/destructor or one of its bases, the behavior is undefined.
Results
If the result type is a pointer and the input pointer is null or cast fails the result is a null pointer.
If the result type is a reference and the cast fails, an exception of type
std::bad_cast
or a type derived from it is thrown.
The pointer version is strongly preferred. Use the reference version when you don't expect the cast to fail.
Examples
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// comment virtual dtor in base - compile error: types are not polymorphic // comment virtual inheritance - compile error: ambiguous base struct base { virtual ~base() = default; }; struct left : virtual base {}; struct right : virtual base {}; struct derived : left, right {}; derived d; // downcasts base& br = d; derived& dr = dynamic_cast<derived&>(br); derived* dp = dynamic_cast<derived*>(&br); // sidecasts left& lr = d; right& rr = dynamic_cast<right&>(lr); right* rp = dynamic_cast<right*>(&lr); // cast to void* (results in a pointer to most derived object) auto vp = dynamic_cast<void*>(&br); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
struct base { virtual ~base() = default; }; struct left : virtual base {}; struct right : virtual base { right(base* b, left* l) { // casts during construction (see the call in the constructor of derived below) // well-defined: right inherits base dynamic_cast<right*>(b); // right is not derived from left // undefined behavior even if the final object inherits right dynamic_cast<right*>(l); } }; struct derived : left, right { derived() : left(static_cast<left*>(this), this) {} }; |
Questions
What if RTTI is disabled?
Most compilers, if given -fno-rtti
option (or equivalent) treat RTTI-requiring dynamic_cast
expressions as ill-formed. For more information see typeof article TOWRITE.
Are
static_cast
casts anddynamic_cast
upcasts only an abstraction? Do they compile to any machine instructions?
They can. Apart from padding issues (caused by different alignment requirements) in case of virtual
base classes (even with no virtual
functions), the compiler may need to adjust this
pointer to correctly point to a subobject of specified type.
const_cast
This cast can unconditionally add or remove const
and/or volatile
on:
references
(potentially multilevel) pointers
(potentially multilevel) pointers to data members
(potentially multilevel) pointers to arrays of known bound
(potentially multilevel) pointers to arrays of unknown bound
Pointers to functions and pointers to member functions are not subject to const_cast
.
The cast is designed to workaround issues with legacy code, particulary C code before C imported const
keyword from C++.
In short, while the cast allows to strip const
and volatile
, it still doesn't justify breaking related rules. It's purely to interact with troublesome non-cv-qualifier-correct legacy code.
1 2 3 4 |
const char* path = "/bin/sh"; int legacy_interface(char* path); // documentation: does not modify path int err = legacy_interface(const_cast<char*>(path)); // ok if path is not modified |
1 2 3 4 5 6 7 |
int x = 1; const int& rx = x; const_cast<int&>(rx) = 2; // ok: object is not actually const const int y = 1; const int& ry = y; const_cast<int&>(ry) = 2; // undefined behavior |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
struct test { int m = 0; void f(int v) const { // this->m = v; // ill-formed: this has type const test* const_cast<test*>(this)->m = v; // ok if *this is not a const object } }; test t; t.f(1); // ok const test ct; ct.f(1); // invokes undefined behavior void (test::* pmf)(int) const = &test::f; // ill-formed: can not cast member function constness // const_cast<void(test::*)(int)>(pmf); |
reinterpret_cast
Similarly to const_cast
, reinterpret_cast
does not compile to any machine instructions (except when converting between integers and pointers or on obscure architectures where pointer representation depends on its type). It is purely a compile time directive which instructs the compiler to treat expression as if it had a different type.
Because the cast allows to explicitly violate the type system (except const
and volatile
for which const_cast
has to be used) and multiple implementation and platform shenanigans are in play, there are numerous rules that specify different requirements and guarantees. The cast is used practically only for platform-specific code or code that relies on particular implementation guarantees (larger than the standard).
The standard technically allows many obscure platforms, but in practice many (if not majority or all) platforms have relatively simple pointer implementation where:
sizeof(void*) == sizeof(T*)
for anyT
sizeof(void*) == sizeof(std::uintmax_t)
sizeof(void*) == sizeof(std::uintptr_t)
alignof(void*) == alignof(std::uintptr_t)
std::uintmax_t
andstd::uintptr_t
are the same typevoid*
can be safely converted to and from non-member function pointers.
Note that pointers to member functions are not the same as pointers to free functions as the former have to support virtual
functions and multiple inheritance. Such pointers typically hold multiple addresses and/or offsets. Relevant SO question.
Integral convertions
A pointer can be converted to any integral type large enough to hold all values of its type.
A value of any integral or enumeration type can be converted to a pointer type. A pointer converted to an integer of sufficient size and back to the same pointer type is guaranteed to have its original value, otherwise the resulting pointer cannot be dereferenced safely (the round-trip conversion in the opposite direction is not guaranteed; the same pointer may have multiple integer representations).
This is simple. Pointers are just memory addresses (the pointed type is purely an abstraction information for the compiler) so their values can aswell be held by integral types. If implementation supports it, <cstdint>
delivers std::uintptr_t
(not a separate type but an alias, usually for unsigned long long
) which is capable of holding pointers to void
. There is also std::intptr_t
but I have never seen anyone using it, signed
integral just doesn't seem right here. I recommend to use std::uintptr_t
if it's available because the name clearly specifies intention.
An expression of integral, enumeration, pointer, or pointer-to-member type can be converted to its own type. No changes in value.
I guess this exists to support templates using reinterpret_cast
which accidentally invoke identity convertions in instantiations for some types.
Pointer convertions
A pointer to member function can be converted to pointer to a different member function of a different type. Conversion back to the original type yields the original value, otherwise the resulting pointer cannot be used safely.
A pointer to member object of some class
T
can be converted to a pointer to another member object of another classU
. If alignment ofU
is not stricter than alignment ofT
(that is,alignof(U) <= alignof(T)
), conversion back to the original typeT
yields the original value, otherwise the resulting pointer cannot be used safely.Any pointer to function can be converted to a pointer to a different function type. Calling the function through a pointer to a different function type is undefined, but converting such pointer back to pointer to the original function type yields the pointer to the original function.
On some implementations, a function pointer can be converted to
void*
or any other object pointer, or vice versa. If the implementation supports conversion in both directions, conversion to the original type yields the original value, otherwise the resulting pointer cannot be dereferenced or called safely.
The last point is required on any POSIX compatible system because dlsym
returns pointers to dynamically loaded objects which may be functions.
Aliasing casts
These are the primary uses of reinterpret_cast
. They create references/pointers which alias an existing object and allow access to its byte representation.
Any object pointer type
T*
can be converted to another object pointer typecv U*
. This is exactly equivalent tostatic_cast<cv U*>(static_cast<cv void*>(expression))
, which implies that if alignment ofU
is not stricter than alignment ofT
(that is,alignof(U) <= alignof(T)
), the value of the pointer does not change and conversion of the resulting pointer back to its original type yields the original value.An lvalue expression of type
T
can be converted to reference to another typeU
. The result is an lvalue or xvalue referring to the same object as the original lvalue, but with a different type. No temporary is created, no copy is made, no constructors or conversion functions are called.
Summary
-
use
static_cast
for:converting between different types of data (usually numeric)
downcasting when it's known that the object if of derived type
use
dynamic_cast
for downcasts and sidecasts when dynamic type of the object is not knownuse
const_cast
for dealing for non-cv-qualifier-correct interfaces-
use
reinterpret_cast
for:inspecting object representation
interacting with low-level facilities
pointer-integer convertions