Early Access Program News

Introducing the Constexpr Debugger

“Not a constant expression.” You’ve seen the diagnostics and the note trail, but never the actual state. Until now.

Modern C++ pushes more logic into constexpr/consteval: parsers, tables, DSLs, hashing – real code with real branches. When code fails at compile time, you can either try to guess the reason from the compiler’s notes or de‑constexpr it and hope the runtime reproduction matches what the compiler actually did.

The new Constexpr Debugger available in the first CLion 2025.3 EAP build allows you to stay in the compiler’s world and see what really happens – by stepping through evaluation, inspecting values, and confirming which if constexpr branch fired. Using it helps you understand exactly what the compiler is doing and fix issues faster.

With reflection coming in C++26 (P2996R13), compile‑time code will increase even more. Although none of the compilers support reflection yet, the Constexpr Debugger in CLion provides a foundation that will allow for debugging this metacode in the future.

DOWNLOAD CLION 2025.3 EAP

Features

Here is what you currently can do with the Constexpr Debugger:

  • Start step-by-step debugging from the gutter by clicking the Debug button next to static_assert(...) or a constexpr declarator to check how it was evaluated or why it failed.
  • Use the same actions as in the regular debugger: Step Into, Step Over, Step Out, and Restart. Additionally, you can use Step Backward, the compile-time reverse stepping feature.
  • See what the compiler sees: the call stack, locals, last returns, and template arguments of the current instantiation. 
  • Hover over a variable to see its values, use Evaluate Expression, or navigate to the source code from the call stack.
  • Inspect the entire context when constant evaluation fails to determine when and why it occurred.

How to use the Constexpr Debugger

Basic case

To provide you with an overview of the basic actions that you can perform when debugging constexpr, we’ll use this compile-time Fibonacci cache implementation:

#include <array>

template<std::size_t N>
struct FibCache {
    std::array<int, N + 1> memo{};

    constexpr FibCache() {
        memo[0] = 0; memo[1] = 1;
        for (std::size_t i = 2; i <= N; ++i)
            memo[i] = memo[i - 1] + memo[i - 2];
    }

    constexpr int operator()(int n) const { return memo[n]; }
};

constexpr int get_fibonacci(int n) {
    FibCache<8> cache;
    auto result = cache(n);
    return result;
}

constexpr int k = get_fibonacci(6);          // gutter: step through operator()
static_assert(get_fibonacci(6) == 8, "ok");  // gutter available here too


The following are some basic operations you can perform when debugging:

  • Find the green Debug icon next to the constexpr declarator or static_assert (either will do) and click it to run a Constexpr Debugger session.
  • Use the regular debugger actions, or try Step Backward to time‑travel one step back in your compile‑time evaluation.

  • Inspect the state:
    • Watch how the this->memo array fills during construction by hovering over it in the editor (see the screenshot below).
    • Navigate to the Variables pane to see this, locals (like i), and recent returns.
    • Hover over any variable to see its value.
    • Navigate to the Call Stack to see frames mapped to source.
  • Check the result in the Variables pane after the constructor has precomputed all the Fibonacci numbers:

Edge case: non-constexpr call prevents evaluation

Consider the case when we have a function (fail) that is not constexpr – it’s deliberately called to indicate invalid input. This means that the original expression (parse_int(“hello”)) cannot be evaluated at compile time.

#include <string_view>

void fail(); // note: non-constexpr

consteval int parse_digit(char c) {
    if (c < '0' || c > '9') {
        fail();
    }

    return c - '0';
}

consteval int parse_int(std::string_view s) {
    int result = 0;
    for (std::size_t i = 0; i < s.size(); i++) {
        result = result * 10 + parse_digit(s[i]);
    }

    return result;
}

constexpr auto digit = parse_int("12345");
constexpr auto bad_digit = parse_int("hello"); // start from the gutter

Stepping here lets you see c == 'x', the taken branch, the value of argument c, and the exact point at which the evaluation fails:

Current limitations

  • Breakpoints and Run to Cursor / Force Run to Cursor are not supported in constexpr evaluation.
  • C++20 modules aren’t yet supported: constexpr debugging does not navigate to entities located in imported modules.
  • Stepping may not work properly with some language constructs or compound statements.
  • Some constructs aren’t yet supported by our constexpr evaluator. We track these unsupported cases in YouTrack.

We don’t have a definite timeline for when these missing bits of support might be added, but we’re working on them, so stay tuned!

Share your feedback

Your feedback is essential for improving this feature and making it more robust. We encourage you to try the Constexpr Debugger and share your thoughts and suggestions by submitting a ticket to our issue tracker or commenting below.

DOWNLOAD CLION 2025.3 EAP

image description