Constants, Enumerations, and Initialization
|
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. |
const, constexpr, consteval, constinit
const int limit = 100; // run-time constant: cannot be reassigned, but value need not be known at compile time
constexpr int square(int x) { // may run at compile time OR run time, chosen by the compiler
return x * x;
}
constexpr int nine = square(3); // guaranteed evaluated at compile time (used in a constant expression)
consteval int mustBeCompileTime(int x) { // C++20: an "immediate function" -- MUST run at compile time
return x * x;
}
// int bad = mustBeCompileTime(getRuntimeValue()); // error: argument not a constant expression
constinit int initializedOnce = square(4); // C++20: guarantees compile-time *initialization*, but the
// variable itself may still be mutated later at run time -- unlike
// constexpr, constinit does not imply const
constinit solves the "static initialization order fiasco" for namespace-scope variables that must not run a
dynamic initializer at process startup, without forcing them to also be immutable afterward.
Scoped and Unscoped Enumerations
#include <utility> // std::to_underlying (C++23)
#include <iostream>
enum Color { Red, Green, Blue }; // unscoped: enumerators leak into the surrounding scope,
// implicitly convert to int
enum class Suit { Clubs, Diamonds, Hearts, Spades }; // scoped: enumerators must be qualified, no implicit
// conversion to int
int main() {
Color c = Red; // fine, unqualified
Suit s = Suit::Hearts; // must qualify
int underlying = std::to_underlying(s); // C++23: replaces static_cast<int>(s), and works generically
std::cout << underlying << '\n'; // 2
using enum Suit; // C++20: brings Suit's enumerators into scope, unqualified
Suit s2 = Hearts; // now legal without "Suit::"
(void)c; (void)s2;
}
Prefer enum class in new code — unscoped enum is kept mainly for interop with older code and C headers.
Uniform/List Initialization and Its Pitfalls
Brace initialization ({}) works uniformly across variables, aggregates, and containers, and rejects narrowing
conversions that ()/= silently allow:
int a{5}; // direct-list-initialization
int b = {5}; // copy-list-initialization
// int c{5.5}; // error: narrowing conversion from double to int -- caught at compile time
int d(5.5); // allowed, and silently truncates to 5 -- the pitfall {} avoids
std::vector<int> v1{1, 2, 3}; // three elements: 1, 2, 3
std::vector<int> v2(3, 7); // three elements, all 7 -- the "most vexing parse"-adjacent trap: () here
// picks the (count, value) constructor, not a 3-element list
The classic pitfall: std::vector<int> v{3, 7} (braces) means two elements, 3 and 7, while std::vector<int>
v(3, 7) (parens) means three elements, each 7 — braces prefer an initializer_list constructor when one
exists, parens never do.
Default Member Initializers
struct Config {
int retries = 3; // used whenever a constructor doesn't set retries itself
bool verbose = false;
std::string name{"default"};
};
Config c1; // retries=3, verbose=false, name="default"
Config c2{.retries = 5}; // designated initializer overrides just retries; see below
Aggregate Initialization and Designated Initializers
An aggregate (no user-declared constructors, no private/protected non-static data members, no virtual functions, no base classes other than public non-virtual ones) can be initialized member-by-member with braces, optionally naming each member (C++20 designated initializers):
struct Point3D {
double x;
double y;
double z = 0.0;
};
Point3D p1{1.0, 2.0, 3.0}; // positional
Point3D p2{.x = 1.0, .y = 2.0}; // designated -- z keeps its default member initializer, 0.0
// Point3D p3{.y = 2.0, .x = 1.0}; // error: designators must appear in declaration order
See Also
-
Basic Types and Values — implicit vs. explicit conversions.
-
Compile-Time Programming —
constexprfunctions andstatic_assertin depth. -
C: Constants, Enumerations and Initialization — C23’s
constexprapplies to objects only, and its designated initializers are more permissive than C++20’s.