[C++] Compile-time 분기 vs Runtime 분기 (if constexpr)
Runtime branching (런타임 분기)
- 일반적인 if문
- 모든 분기가 컴파일되고, 차후 런타임에서 분기가 이루어진다.
Compile-time branching (컴파일 타임 분기)
- if 뒤에
constexpr
키워드를 추가한다. - 컴파일하는 시점에 분기가 이루어진다. 따라서 해당하지 않는 분기는 제외하고 컴파일된다.
[!note]
constexpr
키워드는 ‘컴파일 타임 상수’임을 알리는 키워드로,const
는 런타임시 동적으로 정해지는 값이어도 괜찮은데 반해constexpr
키워드가 붙는 경우 반드시 컴파일시 값 판별이 가능해야한다.
사례: Template을 사용하는 경우의 branching
Template을 사용하여 같은 코드가 여러 타입으로 instance화 될 수 있는 경우, 분기 처리를 각별히 유의해야한다.
문제 예시
아래는 문제의 코드이다.
using OtherKey = std::string;
using SpecialKey = std::pair<std::string, std::string>;
template <class KeyT>
void someFunc(const outcome<KeyT> &out)
{
// ...
std::string key;
if (std::is_same<KeyT, SpecialKey>::value) // 분기 A (Special key)
key = out.key.first + out.key.second;
else // 분기 B (other key)
key = out.key;
// ...
}
별도로 정의한 Key 타입들이 있고, 이 중 오직 SpecialKey
타입만 std::string
이 아닌 std::pair<std::string, std::string>
타입이다. 따라서 Key 타입을 처리하는데 있어 SpecialKey
타입은 분기 처리해주어야한다.
std::is_same
함수를 통해 타입 판별을 하여 분기 처리하도록 코드가 작성 되어있기 때문에, SpecialKey
타입은 분기 A만을 타고 그 외의 Key들은 분기 B만을 타 정상 동작할 것으로 기대할 수 있다.
그러나 컴파일 시도시 아래와 같은 오류가 발생한다.
./include/resource.h:104:27: error: no member named 'first' in 'std::string'
104 | key = res.key.first + res.key.second;
| ~~~~~~~ ^
./include/resource.h:107:17: error: no viable overloaded '='
107 | key = res.key;
| ~~~ ^ ~~~~~~~
라인 104에서의 에러는 std::string
타입에서 first
를 접근시도 하였기 때문에 발생한 에러이고, 라인 107에서의 에러는 std::pair<>
타입을 std::string
에 할당하려고 했기에 발생한 에러이다.
원인을 알기 위해 런타임 분기와 컴파일 타임 분기를 구분할 필요가 있다.
Template은 컴파일 타임에서 주어진 코드 틀을 instance화하여 코드를 생성하는 도구로, 컴파일러가 컴파일 중 마주치는 타입들에 대해서 코드가 생성 및 검사된다. 따라서 위 코드 틀에 대해 OtherKey
와 SpecialKey
라는 두 타입에 대해 코드가 아래와 같이 각각 생성되었을 것이다.
// SpecialKey
{
if (true) // 컴파일 시점에 평가됨
key = out.key.first + out.key.second;
else
key = out.key; // 타지 않는 분기여도 타입 체크는 이루어짐 - error
}
// OtherKey
{
if (false) // 컴파일 시점에 평가됨
key = out.key.first + out.key.second; // 타지 않는 분기여도 타입 체크는 이루어짐 - error
else
key = out.key;
}
일반적인 if는 런타임 분기로, 컴파일 시점에서는 분기 처리하지 않고 모든 코드를 컴파일한다. 따라서 ‘타지 않을 분기’여도 type check는 이루어진다.
해결 방법
그렇지만 이 문제를 피하기 위해 함수를 별도로 나누게 되면 템플릿을 사용하는 의미가 없어진다. 템플릿을 그대로 사용하면서 ‘타지 않는’ 분기에 의한 컴파일 타임 오류를 해결하려면 C++17에서 도입된 키워드 constexpr
를 사용하면 된다.
constexpr
는 컴파일 타임에 분기 처리를 해주는 키워드로, ‘타지 않는 분기’는 컴파일 시점에 컴파일 대상에서 제외된다.