Fundamental Types and Type Categories
C++ provides a rich type system that balances low-level control with high-level abstractions. Every type belongs to one of several categories, each with distinct properties regarding copying, lifetime, and memory representation.
Built-in Fundamental Types
| Category | Types | Guaranteed Properties |
|---|---|---|
| Boolean | bool | true or false, implementation-defined size |
| Character | char, wchar_t, char8_t, char16_t, char32_t | Character encoding representation |
| Integer | short, int, long, long long | Minimum sizes specified |
| Floating-Point | float, double, long double | IEEE 754 typical but not mandated |
| Void | void | Incomplete type, used for “no value” |
C++ guarantees size relationships: sizeof(char) ≤ sizeof(short) ≤ sizeof(int) ≤ sizeof(long) ≤ sizeof(long long). However, exact sizes are implementation-defined. Use <cstdint> for fixed-width types: int32_t, uint64_t, etc.
Type Deduction with auto
C++11 introduced auto for automatic type deduction, reducing verbosity while maintaining static typing. The compiler deduces types from initializers at compile time—there’s no runtime overhead.
auto x = 42; // int
auto y = 3.14; // double
auto z = "hello"; // const char*
auto ptr = new int{5}; // int*
// Complex types become manageable
auto it = vec.begin(); // std::vector<T>::iterator
auto lambda = [](int x) { return x * 2; }; // Closure type
Type Deduction Rules
The deduction follows template argument deduction rules with some exceptions:
- Reference collapse:
auto&preserves lvalue references - const stripping:
autoremoves top-level const (useconst auto) - Array decay: Arrays decay to pointers unless
auto&
Auto Type Deduction
int arr[5] = {1,2,3,4,5}; p = arr; // Deduces to int* auto& r = arr; // Deduces to int(&)[5]
decltype: Inspecting Expression Types
While auto deduces from initializers, decltype queries the type of any expression without evaluating it.
int x = 10;
decltype(x) y = 20; // y is int
int& getRef();
decltype(getRef()) z = x; // z is int&
// Combining auto and decltype (C++14 return type deduction)
auto func(int x) -> decltype(x * 2) {
return x * 2; // Return type is int
}
C++14 added decltype(auto) for perfect forwarding of return types:
template<typename Container>
decltype(auto) getElement(Container& c, size_t i) {
return c[i]; // Preserves reference if container returns reference
}
Const Correctness
The const qualifier is fundamental to C++‘s type system, enabling the compiler to enforce immutability guarantees and enable optimizations.
Const Variables
const int max_retries = 3; // Cannot be modified
// max_retries = 5; // Compile error
int const alt_syntax = 5; // Equivalent to const int
Const Pointers vs Pointer-to-Const
The placement of const determines what is immutable:
int value = 10;
const int* ptr1 = &value; // Pointer to const int (can't modify *ptr1)
int* const ptr2 = &value; // Const pointer to int (can't modify ptr2)
const int* const ptr3 = &value; // Both const
*ptr1 = 20; // Error: can't modify through ptr1
ptr1 = nullptr; // OK: pointer itself is mutable
*ptr2 = 20; // OK: can modify through ptr2
ptr2 = nullptr; // Error: pointer itself is const
Mnemonic: Read right-to-left. const int* is “pointer to const int.”
Const Member Functions
Member functions can be marked const, promising not to modify object state:
class Point {
int x_, y_;
public:
int getX() const { return x_; } // Doesn't modify object
void setX(int x) { x_ = x; } // Modifies object
};
const Point p{10, 20};
p.getX(); // OK
// p.setX(5); // Error: can't call non-const method on const object
Type Aliases and Type Identity
C++11 introduced using for type aliases, superseding typedef:
// Old style (C++98)
typedef std::vector<int> IntVector;
typedef void (*FunctionPtr)(int);
// Modern style (C++11+)
using IntVector = std::vector<int>;
using FunctionPtr = void(*)(int);
// Template aliases (only possible with using)
template<typename T>
using Vec = std::vector<T>;
Vec<int> v; // Equivalent to std::vector<int>
Signed vs Unsigned: A Design Choice
Integer types can be signed or unsigned. Mixing them in expressions causes implicit conversions that can be surprising:
int signed_val = -1;
unsigned int unsigned_val = 1;
if (signed_val < unsigned_val) {
// This branch is NOT taken!
// -1 converts to a large unsigned value
}
Best Practice: Prefer signed types for arithmetic, use unsigned only for bit manipulation or when the domain is truly non-negative (e.g., array indices—though even this is debated).
What is the type of `x` in: `const auto& x = 42;`?
Interactive Lab
Waiting for signal...