[C++] Variadic template

전통적인 C나 C++에서 잘못 쓰면 위험한 것 중 하나로 stdarg가 있었다. 이를 이용하면 임의의 개수의 인자를 받는 variadic function을 만들 수 있지만, 함수의 시그니쳐를 통한 타입 체크를 완전히 무시하기 때문에, 함수의 호출자가 규약을 지키지 않고 함수를 사용하더라도 컴파일 타임에 문제를 잡을 수 없기 때문에 실행 시에 여러 가지 문제가 발생했다. 이런 문제를 피하고자 stdarg를 사용하지 않고, 예상되는 인자의 최대 개수를 예상해서 받는 인자의 개수가 다른 함수를 오버로딩하여 사용하는 경우가 더 많았다.

C++11에서는 variadic function을 타입 시스템과 붙여 컴파일 시간에 타입 체크 할 수 있게 하려고 variadic template이라는 문법을 추가하였다. Variadic template을 사용하면 사용된 인자의 타입 별로 instantiation 되기 때문에 바이너리의 크기는 커지지만, 컴파일 타임에 타입 체크를 할 수 있으며 실행 시에 생기는 비용을 줄여준다. Variadic template이 될 템플릿 인자는 ellipsis(...)를 붙여 다음과 같이 사용한다. Variadic template을 사용하는 패턴은 크게 2가지이다.

첫 번째는 다른 함수로 인자를 전달하는 것이다. 대표적으로 make_unique가 이런 경우에 속한다. Possible implementation에서 볼 수 있듯이 make_uniquestd::forward<Args>(args)...를 이용해 T 타입의 생성자에게 모든 인자를 전달한다. 여기서 std::forward 함수 호출 뒤에 ellipsis가 붙어있다는 것이 중요하다. 이를 pack expansion이라고 하는데 std::forward 함수에 인자를 넘기는 패턴을 variadic arguments인 args에 전부 적용하는 것이다. 따라서 argsN 개의 인자였으면 번의 std::forward 함수가 불리게 된다. 이때 단순히 전달하는 것이 아니라 값을 수정해서 다른 함수로 전달할 수도 있다.

위의 예제가 값을 수정하여 전달한 예제이다. 위의 예제는 variadic arguments인 args의 각각을 mapper에 적용한 뒤 reducer에 전달한다. 따라서 args는 모두 같은 타입이어야 하며, mapperargs중 하나를 인자로 받는 함수여야 하고, reducermapper의 리턴 타입들을 args의 개수만큼 받아 하나의 리턴값으로 만드는 함수여야 한다.

두 번째 패턴은 base case를 만들고 재귀적으로 호출하는 함수를 정의하는 것이다. 위의 코드가 재귀적으로 정의한 주어진 숫자를 전부 합치는 코드이다. operator+가 정의된 모든 타입이 아닌 숫자만 인자로 받기 위해서 std::enable_if를 이용해서 인자로 받는 타입을 제한했다. 이번에 설명할 내용은 아니니 그것을 제외하고 보면, 아무런 인자를 받지 않는 int sum() 함수가 base case가 되고, 인자가 하나 이상일 때는 T t와 variadic arguments를 인자로 받는 함수를 정의하였다. 그러면 sum의 인자가 없을 때는 base case가 불리고, 인자가 하나 이상일 때는 두 번째 정의된 함수가 불린다. 이때는 첫 번째 인자는 T t에 매칭되고 나머지 인자는 Args... args에 매칭된다.

아니면 sum 함수를 위와 같이 정의할 수도 있다. 이 sum 함수는 인자 하나를 받는 함수를 base case로 삼았다. 인자를 넘기지 않고 함수를 부르면 컴파일 에러가 발생하고, 인자를 하나만 사용하여 함수를 부를 때 sum(T t) 함수가 불린다. 따라서 인자를 2개 이상 넘길때만 variadic function이 호출된다.

댓글

이 블로그의 인기 게시물

USB 2.0 케이블의 내부 구조

[C++] enum class - 안전하고 쓰기 쉬운 enum

Log Aggregator 비교 - Scribe, Flume, Fluentd, logstash

[Web] SpeechSynthesis - TTS API

[Python] cache 데코레이터로 최적화하기