Typing

Implicit Conversions

Promotions

  • Integer:

    • bool           -> int

    • char           -> int

    • char           -> unsigned int

    • signed char    -> int

    • unsigned char  -> int

    • unsigned char  -> unsigned int

    • short          -> int

    • unsigned short -> int

    • unsigned short -> unsigned int

    • Unscoped enums -> int

    • Unscoped enums -> unsigned int

  • Floating-Point:

    • float          -> double

Conversions

  • Integer:

    • int          -> unsigned int

    • int          -> long

    • int          -> float

    • long         -> short

    • unsigned int -> int

    • enum         -> integer type

  • Floating-Point:

    • float        -> int

    • float        -> double

    • double       -> int

    • double       -> float

    • double       -> long double

    • long double  -> float

  • Boolean:

    • Any arithmetic type -> bool

    • Pointer -> bool

      • nullptr -> false, others -> true

  • Pointer:

    • nullptr -> any pointer type

    • 0 -> pointer (legacy null pointer constant)

    • T* -> void*

    • Derived* -> Base*

  • Array:

    • T[N] -> T*

  • Function:

    • void f() → void (*)()

  • Qualification:

    • T* -> const T*

    • T& -> const T&

Casting

C Style

T value = (T)expression;
T value = T(expression);
  • No clear indication of intent.

  • Attempts multiple conversions in this order:

    • const_cast

    • static_cast

    • static_cast  + const_cast

    • reinterpret_cast

    • reinterpret_cast  + const_cast

  • Generally discouraged in modern C++ because it hides what kind of conversion is occurring.

  • Can silently perform dangerous conversions.

static_cast

  • Compile-time cast.

T value = static_cast<T>(expression);
double d = 3.14;
int i = static_cast<int>(d);

dynamic_cast

Derived* d = dynamic_cast<Derived*>(basePtr);
  • Runtime-checked cast.

  • Requires:

    • At least one virtual function in the base class

    • RTTI enabled

  • If casting pointers:

    • Returns nullptr on failure

  • If casting references:

    • Throws std::bad_cast  on failure

class Base {
    public:
        virtual ~Base() {}
};

class Derived : public Base {};

Base* b = new Derived();

Derived* d = dynamic_cast<Derived*>(b); // Safe

const_cast

int* p = const_cast<int*>(constPtr);
  • Used only to add or remove const  or volatile .

void func(const int* p) {
    int* modifiable = const_cast<int*>(p);
}
  • Removing const is only safe if the original object was not actually declared const.

  • Modifying a truly const object is undefined behavior.

  • Cannot:

    • Change type

    • Change memory layout

    • Perform numeric conversions

reinterpret_cast

  • Low-level, bitwise reinterpretation.

T value = reinterpret_cast<T>(expression);
  • Used for:

    • Converting between unrelated pointer types

    • Pointer ↔ integer conversions

    • Interpreting raw memory

int x = 65;
char* p = reinterpret_cast<char*>(&x);
  • Characteristics:

    • No safety guarantees

    • No runtime checks

    • Highly platform-dependent behavior possible

  • Used primarily in:

    • Systems programming

    • Serialization

    • Memory manipulation

std::bit_cast (C++20)

  • Safer alternative to reinterpret_cast  for value reinterpretation.

  • It enforces strict compile-time guarantees and avoids undefined behavior related to type aliasing and object lifetime.

#include <bit>

float f = 1.0f;
uint32_t bits = std::bit_cast<uint32_t>(f);
  • Performs safe bitwise copy between same-sized types.

Type Of

  • typeid  allows to check the type of an expression.

typeid (expression)
  • This operator returns a reference to a constant object of type type_info  that is defined in the standard header <typeinfo> .

  • A value returned by typeid  can be compared with another value returned by typeid using operators ==  and !=  or can serve to obtain a null-terminated character sequence representing the data type or class name by using its name() member.

  • The string returned by member name of type_info depends on the specific implementation of your compiler and library. It is not necessarily a simple string with its typical type name, like in the compiler used to produce this output.

#include <iostream>
#include <typeinfo>
using namespace std;

int main () {
    int* a,b;
    a = 0; b = 0;
    if (typeid(a) != typeid(b)) {
        std::cout << "a and b are of different types:\n";
        std::cout << "a is: " << typeid(a).name() << '\n';
        std::cout << "b is: " << typeid(b).name() << '\n';
    }
    return 0;
}
  • When typeid is applied to classes, typeid uses the RTTI to keep track of the type of dynamic objects. When typeid is applied to an expression whose type is a polymorphic class, the result is the type of the most derived complete object.

Size Of

x = sizeof (char);
  • The value returned by sizeof is a compile-time constant, so it is always determined before program execution.

Aliasing

  • Syntax:

    • In C and C++:

      typedef existing_type new_type_name;
      
    • In C++:

      using new_type_name = existing_type;
      
    • Both aliases defined with typedef  and aliases defined with using  are semantically equivalent.

    • The only difference being that typedef  has certain limitations in the realm of templates that using  has not.

    • Neither typedef  nor using  create new distinct data types. They only create synonyms of existing types.