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.
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 aconstexpr
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 orstatic_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 (likei
), and recent returns. - Hover over any variable to see its value.
- Navigate to the Call Stack to see frames mapped to source.
- Watch how the

- 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.