Basic Types and Values

This section documents C++23 (ISO/IEC 14882:2024), as published by ISO/IEC JTC1/SC22/WG21 (wg21), verified against the freely available working draft N5046 (eel.is/c++draft) and cppreference.com.

This content was generated with the assistance of AI and should be verified against the working draft and cppreference.com before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Fundamental Types

bool flag = true;

char c = 'a';                 // at least 8 bits; signedness is implementation-defined
signed char sc = -1;
unsigned char uc = 255;

short s = 1;                  // at least 16 bits
int i = 1;                    // at least 16 bits, typically 32
unsigned int u = 1u;
long l = 1L;                  // at least 32 bits
long long ll = 1LL;           // at least 64 bits

float f = 1.0f;                // typically IEEE-754 single precision
double d = 1.0;                // typically IEEE-754 double precision
long double ld = 1.0L;         // wider than double on many platforms, same width on MSVC

The standard only guarantees minimum widths, not exact ones — use <cstdint> when an exact width matters.

Fixed-Width Integers

#include <cstdint>
#include <cstddef>             // std::size_t, std::ptrdiff_t
#include <cassert>

int32_t exact32 = -1;          // exactly 32 bits, if the platform can provide it
uint64_t exact64 = 1;          // exactly 64 bits, unsigned
int_least16_t atLeast16 = 1;   // at least 16 bits, smallest such type
int_fast32_t fast32 = 1;       // at least 32 bits, fastest such type on this platform
std::size_t sz = sizeof(int);  // unsigned, wide enough to hold the size of any object
std::ptrdiff_t diff = 0;       // signed, the type of pointer subtraction

int main() {
    assert(sizeof(int32_t) == 4);
    return 0;
}

Character Types

C++ has seven character types. Five of them carry a distinct encoding role and are the ones you reach for when handling text — signed char and unsigned char are also character types formally, but are normally used as small integers or raw bytes instead (std::byte is the better choice for the latter):

char c = 'a';           // the "narrow" execution character type
wchar_t wc = L'a';       // wide, platform-defined width (16-bit on Windows, 32-bit on Linux) -- avoid in new code
char8_t u8c = u8'a';     // UTF-8 code unit (C++20)
char16_t u16c = u'a';    // UTF-16 code unit
char32_t u32c = U'a';    // UTF-32 code unit / Unicode code point

char8_t is a distinct type from char specifically so UTF-8 strings and byte buffers are no longer overload-ambiguous with each other — see Strings and Text.

bool

bool holds only true/false; every scalar type converts to bool (zero/null is false, anything else is true), which is why if (ptr) and if (count) both compile even though neither is a bool.

auto and decltype

auto deduces a variable’s type from its initializer, following the same rules as template argument deduction:

auto i = 42;                    // int
auto d = 3.14;                  // double
auto& ref = i;                  // int&
const auto& cref = i;           // const int&
auto* ptr = &i;                 // int*

std::vector<int> makeVector();
auto v = makeVector();          // std::vector<int> -- avoids repeating (or getting wrong) a long type name

decltype(expr) yields the declared type of expr without evaluating it, preserving references and cv-qualifiers exactly — useful in generic code where auto alone would strip them:

int x = 0;
int& xr = x;
decltype(xr) xr2 = x;          // int& -- decltype preserves the reference; auto would have deduced int

decltype(auto) forwardResult(int& r) {   // return exactly what the expression's type is
    return r;                             // int& -- decltype(auto) here deduces int&, not int
}

Type Aliases and Alias Templates

using (preferred) or the C-style typedef names an existing type; a using alias can also be a template, which typedef cannot express:

using Distance = double;
using Callback = void(*)(int);

template <typename T>
using Vec = std::vector<T>;    // alias template

Vec<int> numbers = {1, 2, 3};   // same as std::vector<int>

Implicit and Explicit Conversions

Arithmetic types convert implicitly (int to double, narrower to wider integer), which can silently lose information going the other way:

double d = 3.9;
int truncated = d;              // implicit narrowing conversion: 3 (fractional part discarded), often warned on

int big = 300;
char narrowed = big;            // implementation-defined/UB-adjacent: value doesn't fit in char

struct Meters {
    explicit Meters(double value) : value(value) {}   // "explicit" blocks implicit conversion from double
    double value;
};

explicit on constructors and conversion operators (covered fully in Operator Overloading and Conversions) is the main tool for opting a user-defined type out of C++'s permissive implicit-conversion rules. Brace initialization (int x{3.9};) is stricter still: it is ill-formed for narrowing conversions, catching the double-to-int example above at compile time instead of silently truncating.

<limits>, sizeof, and alignof

#include <limits>
#include <iostream>

int main() {
    std::cout << std::numeric_limits<int>::max() << '\n';
    std::cout << std::numeric_limits<int>::min() << '\n';
    std::cout << std::numeric_limits<double>::epsilon() << '\n';
    std::cout << sizeof(int) << '\n';      // size in bytes
    std::cout << alignof(int) << '\n';     // required alignment in bytes
}

std::numeric_limits<T> is the type-generic, constexpr-usable replacement for the C macros INT_MAX/DBL_EPSILON.

See Also