20 topics
← Back to Quick Reference/
Topic 02
DDaattaa TTyyppeess
Integers · Floats · Chars · auto · Conversions · Type Traits
C++17 · Advanced ReferenceThe C++ Type System
01Two categories of types
C++ types divide into fundamental (built into the language) and compound (built from fundamentals — pointers, references, arrays, functions, classes). Every expression has a type known at compile time.
Fundamental
- 1.Integer types —
int,short,long,long long, unsigned variants - 2.Floating-point —
float,double,long double - 3.Character —
char,wchar_t,char8_t,char16_t,char32_t - 4.Boolean —
bool(true/false) - 5.Void —
void(no value; used for functions and pointers) - 6.
std::nullptr_t— type of the literalnullptr
Key rules
- 1.The standard only specifies minimum sizes — use
<cstdint>fixed-width types when exact size matters - 2.
sizeof(char) == 1always; everything else is implementation-defined - 3.Signed integer overflow is
undefined behavior— unsigned overflow wraps - 4.
boolis an integer type —trueconverts to 1,falseto 0 - 5.Floating-point arithmetic is not exact — never use
==to compare doubles - 6.Prefer
autofor local variables; it can't be left uninitialized
Integer Types
02// Signed integers (can hold negative values) short s = 32767; // 16-bit ±32 767 int i = 2147483647; // 32-bit ±2.1 × 10⁹ long l = 2147483647L; // 32-bit on most 64-bit systems long long ll = 9223372036854775807LL; // 64-bit ±9.2 × 10¹⁸ // Unsigned integers (non-negative only, double the positive range) unsigned int u = 4294967295u; // 32-bit 0 – 4.3 × 10⁹ unsigned long long ull = 18446744073709551615ULL; // Fixed-width types (always the same size — prefer these) #include <cstdint> int8_t a = 127; int16_t b = 32767; int32_t c = 2147483647; int64_t d = 9223372036854775807LL; uint8_t e = 255; uint32_t f = 4294967295u; // Size-semantic types size_t n = sizeof(int); // result of sizeof — always unsigned ptrdiff_t p = ptr2 - ptr1; // difference between pointers — signed
| int8_t / uint8_t | Exactly 8 bits. uint8_t is often used as a byte alias. |
| int32_t / uint32_t | Exactly 32 bits. Prefer over plain int when size matters. |
| size_t | Unsigned type for object sizes and array indices. Use for loop counters over containers. |
| ptrdiff_t | Signed type for pointer arithmetic results. |
Floating-Point Types
03float f = 3.14f; // 32-bit ~7 decimal digits double d = 3.14159265358; // 64-bit ~15 decimal digits ← default long double ld = 3.14159265358L; // 80 or 128-bit (platform-dependent) // Literal suffixes 1.0f // float 1.0 // double (default) 1.0L // long double // Special values (from <cmath> / <limits>) #include <limits> double inf = std::numeric_limits<double>::infinity(); double nan = std::numeric_limits<double>::quiet_NaN(); double eps = std::numeric_limits<double>::epsilon(); // ~2.2e-16 // Checking for special values #include <cmath> std::isinf(x); std::isnan(x); std::isfinite(x); // Pitfall: floating-point comparison double a = 0.1 + 0.2; a == 0.3; // ❌ false! (binary representation error) std::abs(a - 0.3) < 1e-9; // ✅ use epsilon comparison
Default to double.
float saves memory but has only ~7 digits of precision — enough for graphics, not for financial or scientific work. long double is 80-bit on x86 but only 64-bit on MSVC and ARM.Character Types
04char c1 = 'A'; // 8-bit, may be signed or unsigned wchar_t c2 = L'Ω'; // wide char — 16-bit (Windows) or 32-bit (Linux) char8_t c3 = u8'a'; // C++20: UTF-8 code unit char16_t c4 = u'α'; // UTF-16 code unit char32_t c5 = U'😀'; // UTF-32 code point (always 32-bit) // Common escape sequences '\n' // newline '\t' // tab '\r' // carriage return '\\' // backslash '\0' // null term. '\'' // single quote // char arithmetic (chars are just small integers) char ch = 'A'; ch + 1; // 66 — arithmetic promotes to int (char)(ch + 1); // 'B' — cast back to char std::isalpha(ch); std::toupper(ch); // <cctype>
Signedness of char is implementation-defined. If you need a small integer, use
signed char or unsigned char explicitly. Use char only for characters.auto & decltype
05// auto — compiler deduces type from initializer auto x = 42; // int auto y = 3.14; // double auto z = 3.14f; // float auto s = "hello"; // const char* auto str = std::string{"hello"}; // std::string // auto strips top-level const/reference — add them back explicitly const auto ci = 42; // const int auto& r = someVector; // reference to someVector's type const auto& cr = vec; // const reference // decltype — type of an expression without evaluating it int a = 5; decltype(a) b = 10; // int decltype(a+b) c = 15; // int (result type of a+b) // decltype(auto) — preserves references and const (C++14) decltype(auto) ref = getRef(); // keeps reference if getRef() returns one // Trailing return type (useful for templates) auto add(int a, int b) -> int { return a + b; }
| auto | Deduces type from initializer. Strips top-level const and references. |
| const auto | Deduced type + const. Use for values you won't modify. |
| auto& | Deduced type + reference. Avoids copies in range-for loops. |
| decltype(x) | Type of expression x, without evaluating it. Preserves refs and const. |
| decltype(auto) | Like auto but preserves reference and const — useful for forwarding return types. |
Conversions & Casts
06// Implicit promotions (safe — no data loss) char → int → long → long long → float → double → long double // Narrowing (unsafe — may lose data, compiler warns with -Wall) double d = 3.99; int i = d; // i = 3 (truncated, not rounded) int j = 300; char c = j; // implementation-defined (likely truncated) // Explicit casts static_cast<int>(3.9) // 3 — safe, compile-time checked static_cast<double>(5) / 2 // 2.5 — int → double before division reinterpret_cast<char*>(&i) // raw memory reinterpretation const_cast<int*>(cptr) // remove const (use sparingly) // Pitfall: integer overflow (undefined behavior for signed types) int max = std::numeric_limits<int>::max(); max + 1; // ❌ undefined behavior — signed overflow is UB // Use unsigned if you need wrapping: uint32_t wraps predictably
Prefer
static_cast over C-style casts. C-style (int)x silently tries static_cast, reinterpret_cast, and const_cast in order — you lose control over which one fires.Literals & Suffixes
07Prefixes & Suffixes
// Integer literal prefixes 255 // decimal 0377 // octal (leading zero) 0xFF // hexadecimal 0b1111'1111 // binary (C++14) // Digit separator (C++14) — readability only, no semantic meaning 1'000'000 // one million 0xFF'FF'FF // 24-bit color 0b1010'1010 // byte with visual grouping // Integer literal suffixes 42u // unsigned int 42L // long 42LL // long long 42ULL // unsigned long long // String literals "hello" // const char[] L"hello" // const wchar_t[] u8"hello" // const char8_t[] (UTF-8) u"hello" // const char16_t[] U"hello" // const char32_t[] R"(raw string)" // raw string — backslashes are literal
Type Traits (C++17)
08#include <type_traits> // Query types at compile time std::is_integral_v<int> // true std::is_floating_point_v<float> // true std::is_same_v<int, int32_t> // platform-dependent! std::is_signed_v<unsigned int> // false std::is_const_v<const int> // true std::is_pointer_v<int*> // true std::is_reference_v<int&> // true // Transform types std::remove_const_t<const int> // int std::remove_reference_t<int&> // int std::add_pointer_t<int> // int* std::make_unsigned_t<int> // unsigned int std::decay_t<const int&> // int (mimics pass-by-value) // Use in if constexpr (see Program Structure topic) template<typename T> void print(T val) { if constexpr (std::is_integral_v<T>) std::cout << "int: " << val; else if constexpr (std::is_floating_point_v<T>) std::cout << "float: " << val; }
| is_integral_v<T> | True for bool, char, short, int, long, long long, and their unsigned/signed variants. |
| is_same_v<T, U> | True only if T and U are exactly the same type — cv-qualifiers matter. |
| decay_t<T> | Mimics pass-by-value decay: removes ref, const, converts arrays to pointers. |
| conditional_t<B,T,F> | Type alias for T if B is true, F otherwise — compile-time ternary for types. |