9-2. 가변 길이 템플릿(Variadic template)
가변 길이 템플릿(Variadic template)
템플릿을 사용하면 가변 길이의 인자를 받을 수 있다.
#include <iostream>
template <typename T>
void print(T arg) {
std::cout << arg << std::endl;
}
template <typename T, typename... Types>
void print(T arg, Types... args) {
std::cout << arg << ", ";
print(args...);
}
int main() {
print(1, 3.1, "abc");
print(1, 2, 3, 4, 5, 6, 7);
}
파라미터 팩(parameter pack)
typename...을 템플릿 파라미터 팩(parameter pack) 이라고 부른다. -> 템플릿 파라미터 팩의 경우 0개 이상의 템플릿 인자들을 나타낸다.
마찬가지로 함수에 인자로 ... 로 오는 것을 함수 파라미터 팩 이라고 부른다. -> 템플릿 파라미터 팩과 마찬가지로, 0개 이상의 함수 인자를 나타낸다.
다음과 같은 print 함수 호출을 생각해볼 때, C++ 컴파일러는 위에서 정의 된 2개의 print 함수 정의를 보면서 어떤 것을 사용할 것인지 선택한다. -> 첫번째 함수는 1개의 인자만 받기 때문에, 제외되고 두번째 함수를 선택하게 된다.
따라서 위 코드는 다음과 같이 처리된다. 그 다음 재귀적으로 인자 2개를 받는 print 를 호출하였다.
두번째 호출 된 print 함수는 다음과 같이 처리된다.
마지막으로 print 함수는 첫번째 정의된 인자가 1개 있는 print 함수로 처리된다. -> 앞서 말했듯 파라미터 팩은 0개 이상의 인자들을 나타내기 때문에, 현재 정의된 두개 함수 모두 호출될 수 있다. -> 하지만 파라미터 팩이 없는 함수의 우선순위가 더 높기 때문에, 첫번째 함수가 호출된다 .
함수 정의 순서를 바꾼다면?
위 코드에서 print 함수 정의 순서를 바꾼다면 컴파일 에러가 발생한다.
C++ 컴파일러는 함수를 컴파일 시에, 자신의 앞에 정의되어 있는 함수들 밖에 보지 못하기 때문이다. -> 따라서
void print(T arg, Types... args)이 함수를 컴파일 할 때,void print(T arg)이 함수가 존재함을 모르는 셈이다.그렇게 된다면, 마지막에 print("abc") 의 오버로딩을 찾을 때, 파라미터 팩이 있는 함수를 택하게 되는데, 그 함수 안에서 print() 가 호출되어 컴파일 에러가 발생한다. (print() 를 정의하지 않았다)

임의의 개수의 문자열을 합치는 함수
가변 길이 템플릿을 활용한 또 다른 예시로, 임의의 길이의 문자열을 합쳐주는 함수를 들 수 있다.
예를 들어서
std::string에서 문자열을 합치기 위해서는 아래와 같이 사용해야 했다.
위 코드는 사실 아래 코드와 동일하다.
문제는 s2 를 더할 때 메모리 할당이 발생하고, s3 를 더할 때 메모리 할당이 또 한번 발생한다는 것이다. -> 합쳐진 문자열의 크기는 미리 알 수 있기에 차라리 한 번에 필요한 만큼 메로리를 할당해버리는 것이 훨씬 낫다.
아래와 같이 코드를 변경한다면, 메모리 할당 1번으로 끝낼 수 있다.
위와 같은 동작을 하는 함수를 다음과 같이 정의할 수 있지만,
StrCat함수가 임의의 개수의 인자를 받아야 된다는 것이 걸린다. -> 이 때, 가변 길이 템플릿을 사용하면 된다.
첫번째 시도
첫번째 시도는 재귀 호출을 통해서 문자열을 계속 더하는 식의 시도를 할 수 있다. -> 위에서 파라미터 팩이 어떻게 동작하는지를 생각한다면 이해하기 쉽다.

하지만 첫번쨰 시도는 하나의 문제가 있는데, 그것은 바로
std::string의operator+를 매번 호출하는 셈이 되어,StrCat의 전달된 인자가 5개라면, 메모리 할당이 최대 5번씩 일어날 수 있다는 것을 의미한다.효율적으로
StrCat을 구현하기 위해서는 합쳐진 문자열의 길이를 먼저 계산한 뒤에 메모리를 할당하고, 그 다음에 문자열을 붙이는 것이 좋다.
두번째 시도
그렇다면 먼저 합쳐진 문자열의 길이를 구한는 함수를 만들어야 할 것이다. -> 물론 이 역시 가변길이 템플릿을 사용해보자
GetStringSize함수는 그냥 임의의 개수의 문자열을 받아서 각각의 길이를 더한 것들을 리턴하게 된다. -> 참고로const char*와std::string모두 잘 작동하게 하기 위해서 인자 1개만 받는GetStringSize의 오버로드를 각각의 경우에 대해서 준비했다.
그렇다면 수정된 StrCat 의 모습은 아래와 같을 것이다. -> GetStringSize 를 통해 확인 된 size 값을 사용해 concat_str 개체의 메모리 공간을 미리 할당한다.
그 다음에는 이제 concat_str 뒤에 나머지 문자열들을 가져다 붙어야 한다. 이 과정을 수행하는 함수를 AppendToString이라고 하고 구현하면 다음과 같다.
완성된 코드는 다음과 같다.

sizeof...
sizeof연산자는 인자의 크기를 리턴하지만 파라미터 팩에sizeof...를 사용할 경우 전체 인자의 개수를 리턴하게 된다.예를 들어 원소들의 평균을 구하는 함수를 생각해보자.

Fold Expression
C++11 에서 도입 된 가변 길이 템플릿은 매우 편리하지만 한 가지 단점이 존재한다. 재귀 함수 형태로 구성해야 하기 때문에, 반드시 재귀 호출 종료를 위한 함수를 따로 만들어야 한다는 것이다.
하지만 C++17 에서 새롭게 도입 된 Fold 형식을 사용한다면, 이를 훨씬 간단하게 표현할 수 있다.
단항 Fold 형식

아래 코드는 C++17 에서 추가된 Fold 형식으로,
위 코드는 아래와 같이 컴파일러에서 해석된다.
여기서 주의해야 할 점은 Fold 식을 사용할 때, 꼭
()로 감싸주어야 한다는 것이다.
이항 Fold 형식

아래 코드처럼 이항 Fold 형식을 사용할 수 있고,
위 코드는 아래와 같이 컴파일러에서 해석된다.
Fold 의 추가적인 기능
,연산자를 사용하면 각각의 인자들에 대해 원하는 식을 실행할 수 있다.

아래 코드처럼 Fold 형식을 사용할 수 있고,
위 코드는 아래와 같이 컴파일러에서 해석된다.
Last updated