Data Types
A data type defines the kind of value a variable can store, how much memory it uses, and what operations can be performed on it.
1. Type Conversion
Type conversions
│
├── Implicit conversions (compiler performed automatically)
│ │
│ ├── Numeric promotions
│ │ ├── bool -> int
│ │ ├── char -> int
│ │ └── float -> double
│ │
│ └── Numeric conversions
│ ├── Widening conversions
│ │ ├── int -> long long
│ │ └── int -> double
│ │
│ └── Narrowing conversions
│ ├── double -> int
│ ├── int -> char
│ └── long long -> int
│
└── Explicit conversions (casts)
├── static_cast<T>(expr)
├── dynamic_cast<T>(expr)
├── const_cast<T>(expr)
├── reinterpret_cast<T>(expr)
└── (T)expr ///< C-style cast
1.1 Implicit
Implicit type conversion is performed automatically by the compiler when an expression of some type is supplied in a context where some other type is expected.
- A numeric promotion converts
smaller numeric typestolarger numeric types(typicallyintordouble). It guarantees to preserve the value being converted. - A numeric conversion is any arithmetic type conversion that is not a numeric promotion. Numeric conversions may lose data or precision.
/// @brief Numeric promotion: char c = 100; // char int i = c; // char -> int /// @brief Numeric conversion: double d = 3.14; int x = d; // double -> int
1.2 Explicit
Explicit type conversion is requested directly by the programmer C++ supports five cast operators:
static_cast: used for well-defined conversions between related typesdynamic_cast: used for safe downcasting in polymorphic class hierarchiesconst_cast: used to add or removeconstreinterpret_cast: used for low-level reinterpretation of bits or addresses- C-style cast: inherited from C
/// @brief static_cast double d = 3.14; int i = static_cast<int>(d); /// @brief dynamic_cast Base* b = new Derived; Derived* p = dynamic_cast<Derived*>(b); /// @brief const_cast const int x = 10; int* px = const_cast<int*>(&x); /// @brief reinterpret_cast int x = 65; char* p = reinterpret_cast<char*>(&x); /// @brief C-style cast double d = 3.14; int i = (int)d;
2. Type Aliases
A type alias creates an alternative name for an existing type. C++ supports two ways to create type aliases:
-
Type Alias:
- Introduced in C++11.
- Uses the
usingkeyword.
-
Typedef:
-
The traditional way of creating type aliases.
-
Uses the
typedefkeyword./// @brief Type Alias using MyDouble = double; using IntVector = std::vector<int>; template <typename T> using Vec = std::vector<T>; Vec<int> numbers; Vec<double> values; /// @brief Typedef typedef double MyDouble; typedef std::vector<int> IntVector;
-
3. Type Deduction
Type deduction allows the compiler to determine the type of an object from its initializer.
- Type deduction can be performed using the
autokeyword ordecltype.autodeduces a type from an initializer.decltypededuces the exact type of an expression.
3.1. auto (C++11)
The auto keyword allows the compiler to automatically deduce the type of a variable from its initializer.
-
automust have an initializer so the compiler has a type to deduce from.auto i{42}; // int , 42 is the initializer auto d{3.14}; // double auto s{"hello"}; // const char* -
Top-level
constis dropped during type deduction.const int x{5}; auto a{x}; // int const auto b{x}; // const int -
References are dropped unless explicitly requested.
int value{10}; int& ref{value}; auto a{ref}; // int auto& b{ref}; // int& -
Use
auto*when deducing pointers.int value{10}; int* ptr{&value}; auto p{ptr}; // int* auto* q{ptr}; // int* -
The
autokeyword can also be used with a trailing return type, where the return type is written after the parameter list.int add(int x, int y) { return x + y; } // Equivalent auto add(int x, int y) -> int { return x + y; }
3.2. decltype
decltype(expr) evaluates the type of an expression at compile time without executing the expression.
- It is commonly used in templates and generic programming when the exact type of an expression is not known in advance.
- Unlike
auto,decltypepreserves references and const qualifiers. - Most commonly used in template and library code.
- For ordinary application code,
autois often sufficient.int x{1}; double y{2.0}; decltype(x + y) result{3.5}; // Since the expression has type `double`, the declaration above is equivalent double
4. Runtime Type Flexibility
C++ is a statically typed language. The type of a variable is normally known at compile time.
C++ also provides several mechanisms that allow a program to work with objects whose types are not known until runtime.
4.1. void*
A void pointer can store the address of an object of any type.
int value = 100;
void ptr(&value);
int int_ptr = static_cast<int*>(ptr); // not type-safe
4.2. <any> (C++17)
std::any is a type-safe container that can hold a value of any copyable type.
The non-member any_cast functions provide type-safe access to the contained object.
// any types
std::any a = 1;
std::cout << a.type().name() << ": " << std::any_cast<int>(a) << '\n';
// bad cast
try {
a = 1;
std::cout << std::any_cast<float>(a) << "\n";
} catch(const std::bad_any_cast& e){
std::cout << e.what() << "\n";
}
// has value
a = 2;
if (a.has_value()){
std::cout << a.type().name() << ": " << std::any_cast<int>(a) << '\n';
}
// reset
a.reset();
if (!a.has_value()){
std::cout << "no value\n";
}
// pointer to contained data
a = 3;
int* i = std::any_cast<int>(&a);
std::cout << *i << '\n';
4.3. dynamic_cast
Dynamic cast performs safe conversions within a polymorphic inheritance hierarchy.
- Uses
RTTIto verify conversions at runtime.Base* ptr = new Derived; Derived* d = dynamic_cast<Derived*>(ptr);
4.4. Virtual Functions
Virtual functions are enable runtime polymorphism.
- The function called depends on the object’s dynamic type.
Base* ptr = new Derived; ptr->print();
4.5. std::variant (C++17)
std::variant is a type-safe union that can store one value from a fixed set of types.
std::variant<int, double, std::string> value;
value = 42;
value = "Hello";
5. Run-Time Type Information (RTTI)
RTTI provides information about an object’s actual type during runtime. It is primarily supported through:
dynamic_casttypeid
5.1. typeid
typeid(obj) returns type information for an expression or object.
- Defined in the
<typeinfo>header. - The returned type information is represented by
std::type_info.#include <typeinfo> int x{10}; std::cout << typeid(x).name(); /// @brief When RTTI is enabled and `Base` is polymorphic (has at least one virtual function), /// `typeid(*ptr)` reports the dynamic type (`Derived`) rather than the static type (`Base`). Base* ptr = new Derived; std::cout << typeid(*ptr).name(); typeid(ptr)returns the type of the pointer (Base*).typeid(*ptr)returns the type of the object being pointed to (Derived).- The result of
.name()is implementation-defined and may not be human-readable.
6. Compile-Time Type Flexibility
6.1. Type Traits (<type_traits>)
A type traits provide information about types at compile time. It ’s commonly used in templates and generic programming.
std::is_integral_v<int>; // true
std::is_pointer_v<int*>; // true
std::is_const_v<const int>; // true
/// @brief Type transformations
std::remove_const_t<const int>; // int
std::add_pointer_t<int>; // int*
6.2. Concepts (C++20)
- Specify compile-time requirements for template parameters.
- Improve template error messages and readability.
template<typename T> requires std::integral<T> T add(T a, T b) { return a + b; }