본문으로 건너뛰기
Previous
Next
C++ if constexpr | Compile-Time Branching in Templates

C++ if constexpr | Compile-Time Branching in Templates

C++ if constexpr | Compile-Time Branching in Templates

이 글의 핵심

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.

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

#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

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

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

#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

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

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

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

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

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:

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

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):

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


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Use if constexpr to discard untaken branches during instantiation—unlike runtime if, avoiding ill-formed code in unu… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

C++, if constexpr, C++17, Compile-time, Templates 등으로 검색하시면 이 글이 도움이 됩니다.