← Back to list

if constexpr: Compile-Time Branching in C++17

C++17 introduced if constexpr to support compile time conditional branch selection. If the condition is true at compile time, only the true…

Sagar in Towards Dev · 2026-01-14 08:07 · 0 claps · 6.7 min read
#cpp17 #constexpr #template-metaprogramming #cpp-tutorial #c-plus-plus-tutorial
Open on Medium ↗
Wiki topics: 💻 · Programming 🏛️ · Politics

if constexpr: Compile-Time Branching in C++17

C++17 introduced if constexpr to support compile time conditional branch selection. If the condition is true at compile time, only the true branch is used to generate code. If the condition is false at compile time, only the false branch is used to generate code.

  • The non-selected branch is discarded before semantic analysis (name lookup, type checking, overload resolution, and template processing).
  • Because it is never used to generate code, it produces no symbols and no machine instructions, and therefore contributes nothing to the final binary.

A Simple philosophy: Evaluate at compile time, keep only what is needed, discard the dead code.

if constexpr (a_condition)
    /* do something */;
else if constexpr (b_condition)
    /* do another thing */;
else
    /* do default */;

Note: The conditions must be evaluated from a constant expression.

// OK
constexpr int i=10;
if constexpr(i==10)
    std::cout<<"hi";

The below will not compile since i is not a constant expression.

// NG
int i = 10;
if constexpr (i == 10)  // Error: i is not a constant expression
    std::cout << "hi";

Lets understand how it works with a simple example:

#include <iostream>

int main() {
  constexpr int i = 10;
  if constexpr (i == 10)
      std::cout << "hi";
  else 
    std::cout << "bye";
}

Lets have a look at the assembly:

.LC0:
        .string "hi"
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 10
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:std::cout
        call    std::basic_ostream<char, std::char_traits<char>>& std::operator<<<std::char_traits<char>>(std::basic_ostream<char, std::char_traits<char>>&, char const*)
        mov     eax, 0
        leave
        ret

Since the compiler knows the answer at compile time it only emits:

.LC0:
    .string "hi"

mov     esi, OFFSET FLAT:.LC0
mov     edi, OFFSET FLAT:std::cout
call    std::operator<<

So the above code compiles as if we have (no if else branching) like this:

constexpr int i=10;
std::cout<<"hi";

The same code if we write with normal if-else it would include both the conditional branches in the assembly an in-turn in the binary. Look the data section contains both hi and bi . Also the cmp jne instruction(for the if-else).

Note: When if constexpris used there is zero runtime overhead for the branch decision, as the “choice” is made entirely by the compiler.

Lets Deep Dive into Further Details

Ok at this point you have a basic understanding of what if constexpr does and how it does. Now its time to take an example and answer the following questions:

  • How its helpful in Template Meta-Programming by providing a cleaner alternative to SFINAE (Substitution Failure Is Not An Error) or tag dispatching ?
  • How it differ from normal if-else ?

So here is the problem statement of our example: We want to create a program that prints a value differently based on whether its type is int or std::string. Specifically:

  • For int: print the value doubled
  • For std::string: print the value in uppercase

These are the various approaches i can think of !!!!

Approach 1: Function Overloading:

#include <iostream>
#include <string>
#include <algorithm>

void printValue(int value) {
    std::cout << "Integer: " << value * 2 << std::endl;
}

void printValue(const std::string& value) {
    std::string upper = value;
    std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper);
    std::cout << "String: " << upper << std::endl;
}

int main() {
    printValue(42);        // Integer: 84
    printValue("hello");   // String: HELLO
}

Simple right !! But it has the following drawbacks:

  • This works but doesn’t scale well when you need to support many types or want to use template Meta-Programming
  • The function selection happens at runtime based on the argument type, which adds runtime overhead even though we are passing compile time constants as argument and the compiler is capable of evaluating this at compile time.
  • You need to write separate functions for each type you want to support

Ok fair enough lets use templates to reduce the problem of writing many overloaded functions by replacing with a generic implementation and let compiler generate the functions for us. After all thats what Template's do right.

Approach 2: Templates with Regular if (Fails!)

#include <iostream>
#include <string>
#include <algorithm>
#include <type_traits>

template<typename T>
void printValue(T value) {
    if (std::is_same_v<T, int>) {
        std::cout << "Integer: " << value * 2 << std::endl;
    }
    else if (std::is_same_v<T, std::string>) {
        std::string upper = value;
        std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper);
        std::cout << "String: " << upper << std::endl;
    }
}

Oops This fails to compile! Here’s why:

When you call printValue(42), the compiler instantiates the entire function body with T = int. This means both branches of the if statement get compiled:

  • The first branch tries to compile value * 2 with int — this works fine
  • The second branch tries to compile string operations on an intcompilation error!

Even though the runtime condition std::is_same_v<T, std::string> is false for int, the compiler still checks that the code inside that branch is syntactically valid for the given type. So what is a compile-time solution. How about using the famous Tag Dispatch with Template function approach, lets try that.

Approach 3: Templates with Tag Dispatch

#include <iostream>
#include <string>
#include <algorithm>
#include <type_traits>

// Tag types
struct IntTag {};
struct StringTag {};

// Helper to select the appropriate tag
template<typename T>
using SelectTag = std::conditional_t<
    std::is_same_v<T, int>, 
    IntTag,
    std::conditional_t<std::is_same_v<T, std::string>, StringTag, void>
>;

// Implementation for int
template<typename T>
void printValueImpl(T value, IntTag) {
    std::cout << "Integer: " << value * 2 << std::endl;
}

// Implementation for string
template<typename T>
void printValueImpl(T value, StringTag) {
    std::string upper = value;
    std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper);
    std::cout << "String: " << upper << std::endl;
}

// Main template function that dispatches to the correct implementation
template<typename T>
void printValue(T value) {
    printValueImpl(value, SelectTag<T>{});
}

int main() {
    printValue(42);                      // Integer: 84
    printValue(std::string("hello"));    // String: HELLO
}

This code works and we have got the compile-time solution — the compiler selects the correct function at compile time based on the tag type, so there’s no runtime overhead. But look at the code, its verbose, ugly, and tedious to maintain. You need to create tag types, multiple implementation functions, and a dispatcher function for every type-dependent operation.

Since you already know about if constexpr lets use that and re-write the solution.

Approach 4: The Elegant Solution — if constexpr

#include <iostream>
#include <string>
#include <algorithm>
#include <type_traits>

template<typename T>
void printValue(T value) {
    if constexpr (std::is_same_v<T, int>) {
        std::cout << "Integer: " << value * 2 << std::endl;
    }
    else if constexpr (std::is_same_v<T, std::string>) {
        std::string upper = value;
        std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper);
        std::cout << "String: " << upper << std::endl;
    }
    else {
        std::cout << "Unsupported type" << std::endl;
    }
}

int main() {
    printValue(42);                      // Integer: 84
    printValue(std::string("hello"));    // String: HELLO
    printValue(3.14);                    // Unsupported type
}

Neat, Simple and Elegant. The condition is evaluated at compile time

  • Only the branch that matches is actually compiled
  • The other branch is discarded and never checked for validity
  • When printValue(42) is called, only the int branch exists in the compiled code, for printValue(std::string("hello")) the string branch and for printValue(3.14) the code under else branch.

If we look at the assembly and carve out the asembly generated for printValue(42) we can clearly see there is no branching instruction.No jmp, je, jne, or any conditional branching instructions! The function is just straight-line code that prints the doubled integer. The if constexpr branches have been completely eliminated - only the code for the int case exists in this instantiation.

void printValue<int>(int):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], edi
        mov     esi, OFFSET FLAT:.LC2        ; "Integer: "
        mov     edi, OFFSET FLAT:std::cout
        call    std::operator<<<...>         ; cout << "Integer: "
        mov     rdx, rax
        mov     eax, DWORD PTR [rbp-4]
        add     eax, eax                     ; value * 2
        mov     esi, eax
        mov     rdi, rdx
        call    std::ostream::operator<<(int) ; print the doubled value
        ; ... print endl ...
        leave
        ret

Notable Benefits of if constexpr

  • Template Meta-Programming:if constexpris a game-changer for template Meta-Programming. Before C++17, Meta- Programming required complex techniques like SFINAE (Substitution Failure Is Not An Error), template specialization, or tag dispatch. Now, you can write straightforward, readable code. Armed with C++20 concepts, if constexpr becomes even more powerful:
template<typename T>
auto getValue(T container) {
    if constexpr (std::is_array_v<T>) {
        // Handle C-style arrays
        return container[0];
    }
    else if constexpr (requires { container.front(); }) {
        // Handle containers with front() method
        return container.front();
    }
    else if constexpr (requires { *container.begin(); }) {
        // Handle containers with iterators
        return *container.begin();
    }
    else {
        return container;  // Return as-is for other types
    }
}
  • Platform-Specific Code: if constexpr is perfect for writing cross-platform libraries where different code paths are needed based on the target platform. This allows you to write platform-specific optimizations (like SIMD intrinsics for x86 vs ARM) or use different APIs based on the operating system, all in a single codebase without the troublesome preprocessor macros.
template<typename T>
void serialize(T* ptr) {
    if constexpr (sizeof(void*) == 8) {
        // 64-bit platform
        uint64_t address = reinterpret_cast<uint64_t>(ptr);
        // Use 64-bit serialization format
    }
    else if constexpr (sizeof(void*) == 4) {
        // 32-bit platform
        uint32_t address = reinterpret_cast<uint32_t>(ptr);
        // Use 32-bit serialization format
    }
}
  • Performance Optimization: You can create generic algorithms that optimize based on type properties. These compile-time decisions ensure zero runtime overhead while keeping code maintainable and type-safe.
template<typename T>
void processData(std::vector<T>& data) {
    if constexpr (std::is_trivially_copyable_v<T>) {
        // Use fast memcpy-based operations for POD types
        std::memcpy(/* ... */);
    }
    else {
        // Use proper copy constructors for complex types
        for (auto& item : data) { /* ... */ }
    }
}

This is the essence of if constexpr - a powerful tool that embodies modern C++'s commitment to zero-overhead abstractions and clean, maintainable code.

Found this article helpful? Please Clap 👏 and follow for more C++ and system programming content.

If you’re serious about passing technical interviews, try PracHub. It helped me structure my preparation with advanced mock sessions. [**Check it out here**] and start practicing. (Disclosure: This is an affiliate link).


메타데이터
post_id
ebe46c3da9cf
slug
cpp-if-constexpr-ebe46c3da9cf
url
https://towardsdev.com/cpp-if-constexpr-ebe46c3da9cf
canonical_url
https://towardsdev.com/cpp-if-constexpr-ebe46c3da9cf
author_url
https://medium.com/@sagarmadala
status
ok
fetched_at
2026-06-23 06:34:20