Skip to main content
    Courses/C++/Constexpr & Compile-Time Programming

    Lesson 22 • Advanced

    Constexpr & Compile-Time Programming

    By the end of this lesson you'll be able to push real work out of run time and into the build itself — writing constexpr variables and functions, telling const, constexpr, and consteval apart, branching at compile time with if constexpr, and proving facts with static_assert so mistakes fail the build, not your users.

    What You'll Learn

    • Write constexpr variables and functions that run at compile time
    • Tell const, constexpr, and consteval apart and pick the right one
    • Branch on types at compile time with if constexpr
    • Check facts at build time with static_assert
    • See why the same constexpr function can run at compile time or run time
    • Explain why compile-time work means faster, safer programs

    💡 Real-World Analogy

    Imagine a chef prepping for a dinner service. Some work can be done ahead of time — chopping vegetables, making stock, baking bread. That's compile-time work: done once, before any customer arrives, so service is fast. Other work has to wait for the order — you can't sear a steak until the customer says "medium-rare". That's run-time work. constexpr is you telling the compiler "this can be prepped ahead" — if all the ingredients (the inputs) are known in advance, the answer is ready and waiting before the program ever runs. The result is baked into the binary at zero runtime cost.

    1. constexpr Variables & Functions

    Put constexpr in front of a function and you're telling the compiler "this can be evaluated while compiling". Put it in front of a variable and you're telling it "evaluate this now, at compile time". If the inputs are known during the build, the work happens then and the answer is stored as a plain constant — nothing is computed when the program runs. Read this worked example carefully, run it, then you'll write your own.

    Worked example: compile-time factorial

    Read every comment, run it, and check the output matches.

    Try it Yourself »
    C++
    #include <iostream>
    using namespace std;
    
    // 'constexpr' on a function means: "this CAN run at compile time."
    // If every argument is known to the compiler, the answer is worked out
    // during the build and baked straight into the program as a constant.
    constexpr int factorial(int n) {
        if (n <= 1) return 1;          // base case
        return n * factorial(n - 1);   // 5 * 4 * 3 * 2 * 1
    }
    
    int main() {
        // 'constexpr' on a variable means: "compute this at compile time."
        // The compiler ru
    ...

    Your turn. The program below is almost complete — fill in the three blanks marked ___ using the // 👉 hints, then run it. You're adding the constexpr keyword in the two right places and a static_assert to check the result at build time.

    🎯 Your turn: a constexpr square function

    Fill in the ___ blanks, then check your output against the expected lines.

    Try it Yourself »
    C++
    #include <iostream>
    using namespace std;
    
    // 🎯 YOUR TURN — fill in the blanks marked ___ then press "Try it Yourself".
    
    // 1) Make this a function that CAN run at compile time.
    ___ int square(int x) {     // 👉 replace ___ with the keyword constexpr
        return x * x;
    }
    
    int main() {
        // 2) Force this to be computed at compile time.
        ___ int nine = square(3);   // 👉 replace ___ with constexpr   (nine == 9)
    
        cout << "3 squared = " << nine << endl;
    
        // 3) Prove it was a compile-time
    ...

    2. const vs constexpr vs consteval

    These three keywords are easy to mix up, but each answers a different question. const asks "can this change?" — no, but it might still be decided at run time. constexpr asks "is this known at compile time?" — yes, and that also makes it const. consteval (C++20) is the strict version: "this must run at compile time", with no runtime fallback at all. The worked example shows all three side by side.

    Worked example: const, constexpr & consteval

    See how each keyword behaves with compile-time vs runtime values.

    Try it Yourself »
    C++
    #include <iostream>
    using namespace std;
    
    // consteval (C++20) = "MUST run at compile time." Calling it with a
    // runtime value is a hard error — there is no runtime fallback.
    consteval int cube(int x) { return x * x * x; }
    
    int main() {
        int runtimeValue = 5;          // not known to the compiler in advance
    
        // 'const' = "this never changes after it is set." It can be set
        // from a RUNTIME value — const says nothing about WHEN it is known.
        const int a = runtimeValue;    // OK: va
    ...

    🔎 Deep Dive: the same function, two modes

    A constexpr function is not guaranteed to run at compile time — it's allowed to. The deciding factor is the inputs. Feed it compile-time constants and use the result where a constant is required, and it runs during the build. Feed it a runtime variable and the very same function runs at run time instead.

    constexpr int twice(int x) { return x * 2; }
    
    constexpr int a = twice(5);   // compile time -> a is the constant 10
    int n = readInput();
    int b = twice(n);             // run time     -> n is only known then

    This is the superpower: one implementation serves both worlds. Want to forbid the runtime path entirely (so a runtime call is a compile error)? Use consteval instead of constexpr.

    3. Branching at Compile Time with if constexpr

    if constexpr (C++17) chooses a branch while compiling, based on a constant or a type trait like is_integral_v<T>. The branch that isn't taken is thrown away before it's even type-checked — so one template can do genuinely different things for different types. A normal if can't do that, because both branches must compile for every type. This is the standard way to write code that adapts to T.

    Worked example: if constexpr by type

    One template, different behaviour per type — chosen at compile time.

    Try it Yourself »
    C++
    #include <iostream>
    #include <type_traits>
    #include <string>
    using namespace std;
    
    // 'if constexpr' picks a branch at COMPILE time, based on the type T.
    // The branch that is NOT taken is removed entirely — it never has to
    // even compile for that type. A plain 'if' could not do this safely.
    template <typename T>
    string describe(const T& value) {
        if constexpr (is_integral_v<T>) {
            // Only compiled when T is a whole-number type (int, long, bool...)
            return "Integer: " + to_stri
    ...

    Now you try. The template below should double whole numbers but halve decimals. Fill in the one blank so the branch is chosen at compile time, then run it:

    🎯 Your turn: pick a branch at compile time

    Make the if a compile-time branch, then check the two outputs.

    Try it Yourself »
    C++
    #include <iostream>
    #include <type_traits>
    using namespace std;
    
    // 🎯 YOUR TURN — fill in the blanks marked ___ then run it.
    
    // This function should double integers but halve floating-point numbers.
    template <typename T>
    T transform(T value) {
        // 1) Make this a COMPILE-TIME branch (a normal 'if' won't do here).
        if ___ (is_integral_v<T>) {   // 👉 replace ___ with constexpr
            return value * 2;         // integers get doubled
        } else {
            return value / 2;         // everyt
    ...

    Pro Tips

    • 💡 Prove it ran at compile time with static_assert or by using the value as an array size — both only accept genuine compile-time constants.
    • 💡 Reach for consteval (C++20) when a runtime call would be a bug — it turns that mistake into a compile error.
    • 💡 Lookup tables are perfect candidates: trig tables, CRC tables, and hash seeds can be generated once at compile time and shipped as constants.
    • 💡 Keep constexpr functions simple. They must be pure (no I/O, no surprises) to be usable in a constant context.

    Common Errors (and the fix)

    • "call to non-constexpr function" / "is not a constant expression": you called something that isn't constexpr (or does I/O like cout) inside a constexpr context. Everything a constexpr function touches must itself be usable at compile time — make the helper constexpr too, and keep it pure.
    • "the value of 'n' is not usable in a constant expression": you tried to initialise a constexpr variable from a runtime value. constexpr int x = userInput; can't work because userInput isn't known at build time. Use plain const if the value is only known at run time.
    • "call to consteval function ... is not a constant expression": you called a consteval function with a runtime argument. consteval has no runtime mode — pass a literal/constexpr value, or switch the function to constexpr if you genuinely need the runtime path.
    • Array size won't compile: int a[size]; where size isn't a compile-time constant. Mark it constexpr int size = ...; so the size is fixed during the build.
    • Using if where you needed if constexpr: the untaken branch still has to compile and fails for some T. Change it to if constexpr so the dead branch is discarded.

    📋 Quick Reference

    KeywordMeansSince
    constCan't change after it's set (may be a runtime value)C++98
    constexprKnown at compile time if inputs are; also constC++11
    if constexprPick a branch at compile time; drop the otherC++17
    constevalMUST run at compile time (no runtime fallback)C++20
    constinitMUST be initialised at compile timeC++20
    static_assertCheck a condition while compiling; fail the build if falseC++11

    Frequently Asked Questions

    Q: What is the difference between const and constexpr?

    const means a value will not change after it is set, but it can still be decided at run time (for example, const int a = userInput;). constexpr is stronger: the value must be computable at compile time, so the compiler works it out during the build. Every constexpr variable is also const, but not every const is constexpr.

    Q: When does a constexpr function actually run at compile time?

    Only when all of its arguments are themselves compile-time constants and the result is used in a constant context. Call factorial(5) to initialise a constexpr variable and it runs at compile time; call factorial(n) where n is a runtime variable and the same function runs at run time. constexpr means 'allowed at compile time', not 'always at compile time'.

    Q: What does consteval add over constexpr?

    consteval (C++20) removes the runtime fallback. A consteval function must be evaluated at compile time on every call, and calling it with a runtime value is a compile error. Use constexpr when you want the option of both compile-time and runtime use; use consteval when a runtime call would be a bug you want the compiler to catch.

    Q: Why use 'if constexpr' instead of a normal 'if'?

    if constexpr chooses a branch at compile time based on a constant or a type trait, and the branch that is not taken is discarded before it is even type-checked. That lets one template do different things for different types — for example doubling integers but halving floats — where a normal if would force both branches to compile for every type and fail.

    Q: Why move work to compile time at all?

    Two reasons: performance and safety. Performance, because a value computed during the build costs nothing at run time — it is just a constant in the binary. Safety, because static_assert lets you check facts (sizes, ranges, results) while compiling, so a wrong assumption fails the build instead of crashing a user later.

    Mini-Challenge: Compile-Time Fibonacci

    No blanks this time — just a brief and a near-empty canvas (with an outline to keep you on track). Write the constexpr function yourself, force the result at compile time, and use static_assert to prove it. Run it and check your output against the example in the comments.

    🎯 Mini-Challenge: compile-time Fibonacci

    Write a constexpr fib(n), compute fib(10) at compile time, and assert it.

    Try it Yourself »
    C++
    #include <iostream>
    using namespace std;
    
    // 🎯 MINI-CHALLENGE: compile-time Fibonacci
    // 1. Write a constexpr function  int fib(int n)  that returns the nth
    //    Fibonacci number:  fib(0)=0, fib(1)=1, fib(n)=fib(n-1)+fib(n-2).
    // 2. In main, create  constexpr int f10 = fib(10);
    // 3. Add a static_assert that checks  f10 == 55  (fails the build if wrong).
    // 4. Print  "fib(10) = 55".
    //
    // ✅ Expected output:
    //    fib(10) = 55
    
    int main() {
        // your code here
    
        return 0;
    }

    🎉 Lesson Complete

    • constexpr on a function means it can run at compile time; on a variable it means compute it now
    • const = won't change; constexpr = known at compile time; consteval = must be compile time
    • ✅ The same constexpr function runs at compile time or run time depending on its inputs
    • if constexpr picks a branch while compiling and discards the other — perfect for type-based logic
    • static_assert checks facts during the build, so wrong assumptions fail the compile, not the user
    • ✅ Compile-time work means faster programs (zero runtime cost) and safer ones (errors caught early)
    • Next lesson: Modern C++17 & C++20 Features — structured bindings, concepts, ranges, and more

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service