[2026] C++ `if constexpr` | Compile-Time Branching in Templates (C++17)

[2026] C++ `if constexpr` | Compile-Time Branching in Templates (C++17)

이 글의 핵심

Use `if constexpr` to discard untaken branches during instantiation—unlike runtime `if`, avoiding ill-formed code in unused branches for templates.

What is if constexpr?

Compile-time branching for templates (C++17). Discarded branches are not instantiated. 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

template<typename T>
auto getValue(T value) {
    if constexpr (std::is_pointer_v<T>) {
        return *value;  // Only compiled when T is pointer
    } else {
        return value;
    }
}
int x = 10;
auto a = getValue(x);    // Uses else branch
auto b = getValue(&x);   // Uses if branch

Basic usage

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <type_traits>
#include <iostream>
template<typename T>
void print(T value) {
    if constexpr (std::is_integral_v<T>) {
        std::cout << "Integer: " << value << "\n";
    } else if constexpr (std::is_floating_point_v<T>) {
        std::cout << "Float: " << value << "\n";
    } else {
        std::cout << "Other: " << value << "\n";
    }
}
// Usage
print(42);          // "Integer: 42"
print(3.14);        // "Float: 3.14"
print("hello");     // "Other: hello"

if vs if constexpr

Runtime if: All branches must compile

아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

template<typename T>
void processRuntime(T value) {
    if (std::is_pointer_v<T>) {
        std::cout << *value;  // ❌ Error when T=int: cannot dereference int
    } else {
        std::cout << value;
    }
}

if constexpr: Only selected branch compiled

아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

template<typename T>
void processCompileTime(T value) {
    if constexpr (std::is_pointer_v<T>) {
        std::cout << *value;  // ✅ OK: only compiled when T is pointer
    } else {
        std::cout << value;
    }
}

Real-world examples

1. Generic serialization

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <type_traits>
#include <string>
#include <sstream>
template<typename T>
std::string toString(const T& value) {
    if constexpr (std::is_arithmetic_v<T>) {
        return std::to_string(value);
    } else if constexpr (std::is_same_v<T, std::string>) {
        return value;
    } else if constexpr (std::is_same_v<T, const char*>) {
        return std::string(value);
    } else {
        std::ostringstream oss;
        oss << value;
        return oss.str();
    }
}
// Usage
auto s1 = toString(42);          // "42"
auto s2 = toString(3.14);        // "3.140000"
auto s3 = toString("hello");     // "hello"

2. Container optimization

다음은 cpp를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

template<typename Container, typename T>
void addElement(Container& c, const T& value) {
    // Reserve space if container supports it
    if constexpr (requires { c.reserve(1); }) {
        c.reserve(c.size() + 1);
    }
    
    // Use push_back if available, otherwise insert
    if constexpr (requires { c.push_back(value); }) {
        c.push_back(value);
    } else {
        c.insert(c.end(), value);
    }
}
// Works with vector (has reserve + push_back)
std::vector<int> vec;
addElement(vec, 10);
// Works with set (has insert only)
std::set<int> s;
addElement(s, 10);

3. Variadic print with recursion

template<typename T>
void print(const T& value) {
    std::cout << value << "\n";
}
template<typename T, typename....Rest>
void print(const T& first, const Rest&....rest) {
    std::cout << first;
    
    if constexpr (sizeof...(rest) > 0) {
        std::cout << ", ";
        print(rest...);  // Recursive call
    } else {
        std::cout << "\n";
    }
}
// Usage
print(1, 2, 3, "hello", 3.14);
// Output: 1, 2, 3, hello, 3.14

Replacing SFINAE

Before C++17: SFINAE with enable_if

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Overload 1: for integral types
template<typename T>
std::enable_if_t<std::is_integral_v<T>, T>
square(T x) {
    return x * x;
}
// Overload 2: for floating-point types
template<typename T>
std::enable_if_t<std::is_floating_point_v<T>, T>
square(T x) {
    return x * x;
}

After C++17: Single function with if constexpr

아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

template<typename T>
T square(T x) {
    if constexpr (std::is_integral_v<T>) {
        return x * x;
    } else if constexpr (std::is_floating_point_v<T>) {
        return x * x;
    } else {
        static_assert(std::is_arithmetic_v<T>, "T must be arithmetic");
    }
}

Common mistakes

Mistake 1: Using runtime condition

아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

template<typename T>
void process(T value, bool flag) {
    if constexpr (flag) {  // ❌ Error: flag is not constant expression
        // ...
    }
}
// ✅ Fix: use runtime if
if (flag) {
    // ...
}

Mistake 2: Expecting different return types

아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

template<typename T>
auto getValue(T x) {
    if constexpr (std::is_pointer_v<T>) {
        return *x;  // Returns T's pointee type
    } else {
        return &x;  // Returns T*
    }
}
// ❌ Error: inconsistent return types

Fix: Use common return type or std::variant: 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

template<typename T>
auto getValue(T x) -> std::conditional_t<std::is_pointer_v<T>,
                                         std::remove_pointer_t<T>,
                                         T> {
    if constexpr (std::is_pointer_v<T>) {
        return *x;
    } else {
        return x;
    }
}

Mistake 3: Assuming complete elimination

아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

template<typename T>
void debug(T value) {
    if constexpr (false) {
        value.nonExistentMethod();  // ⚠️ May still cause error in some contexts
    }
}

Note: Discarded branches still undergo phase 1 parsing. They must be syntactically valid.

Performance implications

Zero runtime cost: if constexpr is resolved at compile time. Generated code contains only the selected branch. Assembly comparison (GCC 13, -O3): 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

template<typename T>
int process(T x) {
    if constexpr (std::is_integral_v<T>) {
        return x * 2;
    } else {
        return static_cast<int>(x);
    }
}
int a = process(10);     // Assembly: imul eax, 2
int b = process(3.14);   // Assembly: cvttsd2si eax, xmm0

No branching instructions—completely different code paths.

Compiler support

Compilerif constexprNotes
GCC7+Full support
Clang3.9+Full support
MSVC2017 15.3+Full support

Keywords

C++, if constexpr, C++17, compile-time, templates, metaprogramming, SFINAE alternative

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3