Compile-Time Programming
|
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. |
constexpr/consteval/constinit were introduced in
Constants, Enumerations, and
Initialization; this page covers the broader toolkit for computing and branching at compile time.
static_assert
A compile-time assertion — fails the build, with a message, instead of failing at run time:
template <typename T>
struct Vector3 {
static_assert(std::is_arithmetic_v<T>, "Vector3 requires an arithmetic component type");
T x, y, z;
};
static_assert(sizeof(int) == 4, "this code assumes a 32-bit int");
<type_traits>: Querying and Writing Traits
The standard library’s compile-time type-introspection toolkit — used constantly inside generic/template code to branch on a type’s properties:
#include <type_traits>
#include <vector>
struct Base {};
struct Derived : Base {};
static_assert(std::is_integral_v<int>);
static_assert(!std::is_integral_v<double>);
static_assert(std::is_same_v<int, int>);
static_assert(std::is_pointer_v<int*>);
static_assert(std::is_base_of_v<Base, Derived>);
// Writing a custom trait: detect whether T has a "size()" member
template <typename T, typename = void>
struct HasSize : std::false_type {};
template <typename T>
struct HasSize<T, std::void_t<decltype(std::declval<T>().size())>> : std::true_type {};
template <typename T>
inline constexpr bool HasSizeV = HasSize<T>::value;
static_assert(HasSizeV<std::vector<int>>);
static_assert(!HasSizeV<int>);
std::void_t plus a partial specialization is the classic "detection idiom" — since C++20, a requires
expression (see Concepts and Constraints) expresses
the same check far more readably: template <typename T> concept HasSize = requires(T t) { t.size(); };.
std::conditional and std::enable_if vs. Concepts
#include <type_traits>
template <typename T>
using StorageType = std::conditional_t<sizeof(T) <= 8, T, T&>; // pick T or T& based on its size
template <typename T>
std::enable_if_t<std::is_integral_v<T>, T> onlyIntegral(T value) { // SFINAE: this overload only
return value; // participates for integral T
}
std::enable_if/SFINAE was the pre-C++20 way to constrain a template — it works, but produces poor error
messages and clutters signatures. Concepts (requires Numeric<T> from
Concepts and Constraints) express the same
constraint more directly and are strongly preferred in new C++20/23 code; enable_if remains common in
pre-C++20 codebases and libraries that still support C++17.
if constexpr and std::is_constant_evaluated
if constexpr was introduced in Control Flow. C++23’s if
consteval (also covered there) is generally preferred over the older std::is_constant_evaluated() function,
but the latter still appears in code that must also support C++20:
#include <type_traits>
constexpr double approxSqrtOld(double x) {
if (std::is_constant_evaluated()) { // C++20 way to ask "am I running at compile time?"
double guess = x / 2 + 1;
for (int i = 0; i < 20; ++i) guess = (guess + x / guess) / 2;
return guess;
} else {
return __builtin_sqrt(x);
}
}
if consteval { … } (C++23) is preferred over if (std::is_constant_evaluated()) because it cannot be
accidentally combined with other runtime conditions in a way that breaks the compile-time guarantee.
Constexpr Virtual Functions
C++20 allows virtual functions to be constexpr, so polymorphic code can run at compile time as long as the
dynamic type is itself known at compile time:
struct Shape {
constexpr virtual double area() const { return 0.0; }
constexpr virtual ~Shape() = default;
};
struct Square : Shape {
constexpr explicit Square(double side) : side_(side) {}
constexpr double area() const override { return side_ * side_; }
private:
double side_;
};
constexpr double computeArea() {
Square s(4.0); // the dynamic type is statically known here -- eligible for compile-time evaluation
return s.area();
}
static_assert(computeArea() == 16.0);
See Also
-
C: Type-Generic Programming — C23 has
constexprfor objects only — noconsteval, no<type_traits>, and_Genericin place ofif constexpr.