[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화하여 코드를 생성하는 도구로, 컴파일러가 컴파일 중 마주치는 타입들에 대해서 코드가 생성 및 검사된다. 따라서 위 코드 틀에 대해 OtherKeySpecialKey라는 두 타입에 대해 코드가 아래와 같이 각각 생성되었을 것이다.

// 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는 컴파일 타임에 분기 처리를 해주는 키워드로, ‘타지 않는 분기’는 컴파일 시점에 컴파일 대상에서 제외된다.

comments