← SEARCHABLE ARCHIVE
C / C++ · 2026-08-11

C++ from Basics to Mastery — Part 17

Compute at compile time with constexpr and consteval

INTERACTIVE READING MODE →

The articles on this blog are written for teaching purposes. The information may contain mistakes, so verify it against your target compiler and standard library.

C++ can perform selected work before the program starts. constexpr, consteval, and constinit look related, but they express different contracts: constant evaluation, mandatory compile-time calls, and static initialization.

constexpr can run in two contexts

constexpr int Square(int value)
{
    return value * value;
}

constexpr int tableSize = Square(8); // compile time
int input = ReadInput();
int result = Square(input);          // run time

A constexpr function is eligible for constant evaluation; not every call must happen at compile time. When its arguments are constant and the result is used where a constant expression is required, the compiler must evaluate it there.

consteval requires compile-time evaluation

consteval unsigned int HashLiteral(std::string_view text)
{
    unsigned int hash = 2166136261u;
    for (char ch : text)
    {
        hash = (hash ^ static_cast<unsigned char>(ch)) * 16777619u;
    }
    return hash;
}

constexpr auto playerId = HashLiteral("Player");

Every call to a consteval function must be a constant expression. This is useful for asset keys and protocol tags that must be fixed during the build.

A hash may still collide. Computing it at compile time does not prove uniqueness, so collision handling and original-key validation remain necessary.

Removing an invalid branch with if constexpr

template <typename T>
void Serialize(const T& value, Writer& writer)
{
    if constexpr (std::integral<T>)
    {
        writer.WriteInteger(value);
    }
    else
    {
        value.Serialize(writer);
    }
}

Unlike a normal if, the unselected if constexpr branch is discarded for that template specialization. Operations that do not exist for the selected type therefore need not compile in the other branch.

constinit checks static initialization

constinit int globalLimit = 64;

constinit verifies that an object with static storage duration receives static initialization. It does not make the object immutable; that is a separate const property.

Compile-time work can remove runtime initialization and expose mistakes early, but it is not free. Complex metaprogramming increases build time, diagnostics, and sometimes binary size. Use it where a rule is truly fixed and the earlier validation is worth that cost.