Skip to content

C++ constexpr

constexpr means can be evaluated at compile time when used with compile-time inputs. This lets the compiler calculate and validate values before the program starts.

constexpr was introduced in C++11 and became more capable in later standards. The examples in this lesson use C++20.

Goal and prerequisites

After this lesson, you should be able to:

  • create compile-time constants;
  • write a function that works at compile time and runtime;
  • verify a result with static_assert;
  • choose between const, constexpr, and a normal function;
  • recognize when constexpr does not guarantee compile-time evaluation.

You should already understand functions, return values, and const variables.

Brief example

1
2
3
4
5
6
7
constexpr int square(int value)
{
    return value * value;
}

constexpr int area = square(5);
static_assert(area == 25);

The compiler can evaluate square(5) while building the program. If the assertion is false, compilation fails.

Why use constexpr?

Before constexpr, programmers often used macros or repeated literal values:

#define BUFFER_SIZE (64 * 4)

A typed compile-time constant is clearer and follows normal C++ rules:

1
2
3
constexpr int samples_per_block = 64;
constexpr int channel_count = 4;
constexpr int buffer_size = samples_per_block * channel_count;

Unlike a macro, a constexpr variable has a real type, scope, and compiler diagnostics.

Useful compile-time values include:

  • fixed array sizes;
  • protocol constants;
  • unit conversions;
  • small lookup tables;
  • values checked by static_assert;
  • calculations shared by compile-time and runtime code.

const versus constexpr

Both prevent changing a variable after initialization, but they express different guarantees:

Declaration Meaning
const int value value cannot be changed through this name.
constexpr int value value is a compile-time constant and is also const.
1
2
3
4
int read_sensor();

const int latest = read_sensor(); // Valid: value can be known at runtime.
constexpr int limit = 100;        // Must be known at compile time.

This does not compile because a runtime result cannot initialize a constexpr variable:

constexpr int latest = read_sensor(); // Error

Use const for a runtime value that must not change. Use constexpr when the value must be available during compilation.

A constexpr function can run at runtime

Declaring a function constexpr does not force every call to happen during compilation:

#include <iostream>

constexpr int double_value(int value)
{
    return value * 2;
}

int main()
{
    constexpr int fixed = double_value(4); // Compile-time evaluation

    int input{};
    std::cin >> input;
    const int result = double_value(input); // Runtime evaluation

    std::cout << fixed << ' ' << result << '\n';
}

The same function supports both calls. The arguments and the surrounding context determine whether compile-time evaluation is required.

Require a compile-time result

Store the result in a constexpr variable, use it in static_assert, or use it in another context that requires a constant expression.

Compile-time validation with static_assert

static_assert checks a Boolean expression while compiling:

1
2
3
4
5
6
7
constexpr int kilobytes(int count)
{
    return count * 1024;
}

static_assert(kilobytes(2) == 2048);
static_assert(kilobytes(0) == 0);

It creates no runtime test code. Use it for rules that the compiler can prove. Keep runtime tests for values that arrive from files, users, sensors, or the network.

More than one statement

In C++20, a constexpr function can contain normal control flow when every operation used during compile-time evaluation is allowed in a constant expression:

constexpr int absolute(int value)
{
    if (value < 0)
        return -value;

    return value;
}

static_assert(absolute(-7) == 7);
static_assert(absolute(4) == 4);

Do not make a function complicated merely because modern constexpr permits it. A small, pure calculation is easiest to understand and test.

constexpr objects

A class can support compile-time construction and member functions:

class Duration
{
public:
    constexpr explicit Duration(int seconds) : seconds_(seconds) {}

    [[nodiscard]] constexpr int seconds() const
    {
        return seconds_;
    }

private:
    int seconds_;
};

constexpr Duration timeout{30};
static_assert(timeout.seconds() == 30);

This is useful for small value types. It does not mean every class should be rewritten for compile-time use.

These keywords solve different problems:

Keyword Meaning
constexpr A value or function can participate in compile-time evaluation.
consteval Every call to the function must be evaluated at compile time.
constinit A static or thread-local variable must be statically initialized.
1
2
3
4
5
6
consteval int protocol_version()
{
    return 3;
}

constexpr int version = protocol_version();

Use consteval only when a runtime call would be meaningless or invalid. constinit does not make a variable immutable; it controls initialization.

When not to use constexpr

Keep an ordinary function when:

  • its inputs only exist at runtime;
  • it performs input/output;
  • compile-time use provides no practical value;
  • adding compile-time constraints makes the code harder to understand.

constexpr is a guarantee and design signal, not a command to move as much work as possible into compilation.

Predict the behavior

Does this program compile, and what does it print?

#include <iostream>

constexpr int increment(int value)
{
    return value + 1;
}

int main()
{
    constexpr int first = increment(4);
    int value = 9;
    const int second = increment(value);

    std::cout << first << ' ' << second << '\n';
}
Show the answer It compiles and prints:
5 10
`first` is calculated at compile time because it initializes a `constexpr` variable. `second` is calculated at runtime because `value` is not a constant expression.

Compile the examples

Use the complete constexpr_demo.cpp and exercise_starter.cpp files.

1
2
3
cmake -S code/constexpr -B code/constexpr/build
cmake --build code/constexpr/build
ctest --test-dir code/constexpr/build --output-on-failure

Small coding exercise

Open code/constexpr/exercise_starter.cpp. Add:

  1. A constexpr bool is_even(int value) function.
  2. A constexpr int clamp_percentage(int value) function that returns a value from 0 through 100.
  3. At least two static_assert checks for each function.
  4. One runtime call using a value entered by the user.
Show one possible solution
constexpr bool is_even(int value)
{
    return value % 2 == 0;
}

constexpr int clamp_percentage(int value)
{
    if (value < 0)
        return 0;
    if (value > 100)
        return 100;
    return value;
}

static_assert(is_even(8));
static_assert(!is_even(7));
static_assert(clamp_percentage(-5) == 0);
static_assert(clamp_percentage(120) == 100);

Quiz

1. What does constexpr mean for a function?

Choose one answer:

2. Which declaration requires a compile-time value?

Choose one answer:

3. What happens if a static_assert condition is false?

Choose one answer:

4. Which keyword requires every function call to be evaluated at compile time?

Choose one answer:

5. Which value should usually remain a runtime const value?

Choose one answer:

Code-review challenge

A programmer says this calculation is guaranteed to happen during compilation:

1
2
3
4
5
6
7
8
constexpr int square(int value)
{
    return value * value;
}

int input{};
std::cin >> input;
const int result = square(input);

Are they correct?

Show the review No. `input` is only known at runtime, so this call to `square` runs at runtime. The function is allowed to run at compile time, but `constexpr` does not require all calls to do so. If the input is fixed and the result must be computed during compilation, use a constant expression:
constexpr int result = square(6);
static_assert(result == 36);
Do not replace the original code with `consteval`: a value read from the user cannot be evaluated during compilation.

Completion check

You should now be able to:

  • explain the difference between const and constexpr;
  • write a constexpr function usable at compile time and runtime;
  • require compile-time validation with static_assert;
  • explain why a particular call runs at runtime;
  • distinguish constexpr, consteval, and constinit.

Next, continue with compile-time programming or learn how std::optional represents a value that may be absent.

Further reading