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) and int*(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 type T by reinterpreting object representation. This could be thought as a short form of specific usage of reinterpret_cast but the bit cast guarantees no aliasing violations while the keyword alone does not. It's also constexpr.

  • 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 to bool, integer or floating-point type and reverse of it

  • bit-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 overload explicit operator bool to support code like if (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 and dynamic_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 any T

  • sizeof(void*) == sizeof(std::uintmax_t)

  • sizeof(void*) == sizeof(std::uintptr_t)

  • alignof(void*) == alignof(std::uintptr_t)

  • std::uintmax_t and std::uintptr_t are the same type

  • void* 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 class U. If alignment of U is not stricter than alignment of T (that is, alignof(U) <= alignof(T)), conversion back to the original type T 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 type cv U*. This is exactly equivalent to static_cast<cv U*>(static_cast<cv void*>(expression)), which implies that if alignment of U is not stricter than alignment of T (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 type U. 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 known

  • use 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