Programming Languages/C++
가변 템플릿(Variadic Templates) (C++11)
newclass
2025. 3. 28. 08:48
7.4 가변 템플릿(Variadic Templates) (C++11)
C++11에서는 가변 개수의 템플릿 매개변수를 처리할 수 있는 가변 템플릿이 도입되었습니다:
#include <iostream>
#include <string>
// 재귀 종료 조건: 매개변수가 없는 경우
void print() {
std::cout << std::endl;
}
// 첫 번째 매개변수와 나머지 매개변수를 처리하는 가변 템플릿 함수
template <typename T, typename... Args>
void print(T first, Args... args) {
std::cout << first;
if (sizeof...(args) > 0) {
std::cout << ", ";
}
// 재귀적으로 나머지 매개변수 처리
print(args...);
}
// 가변 템플릿을 사용한 합계 계산
// 재귀 종료 함수
template <typename T>
T sum(T value) {
return value;
}
// 가변 매개변수 버전
template <typename T, typename... Args>
T sum(T first, Args... args) {
return first + sum(args...);
}
// 가변 템플릿 클래스
template <typename... Types>
class Tuple;
// 빈 Tuple 특수화
template <>
class Tuple<> {
public:
static constexpr size_t size = 0;
void print() const {
std::cout << "()" << std::endl;
}
};
// 가변 개수의 타입을 가진 Tuple
template <typename T, typename... Rest>
class Tuple<T, Rest...> {
private:
T first;
Tuple<Rest...> rest;
public:
static constexpr size_t size = 1 + sizeof...(Rest);
Tuple(const T& f, const Rest&... r) : first(f), rest(r...) {}
const T& getFirst() const {
return first;
}
const Tuple<Rest...>& getRest() const {
return rest;
}
void print() const {
std::cout << "(" << first;
if (sizeof...(Rest) > 0) {
std::cout << ", ";
rest.print();
} else {
std::cout << ")" << std::endl;
}
}
};
int main() {
// 가변 인자 함수 호출
print(1, 2.5, "Hello", 'A', true);
// 가변 인자 합계 계산
std::cout << "합계: " << sum(1, 2, 3, 4, 5) << std::endl;
std::cout << "합계: " << sum(1.1, 2.2, 3.3) << std::endl;
// 가변 템플릿 클래스 사용
Tuple<int, double, std::string> tuple(42, 3.14, "Hello");
std::cout << "Tuple 크기: " << tuple.size << std::endl;
std::cout << "첫 번째 요소: " << tuple.getFirst() << std::endl;
std::cout << "Tuple 내용: ";
tuple.print();
return 0;
}
가변 템플릿의 주요 특징:
- typename... 또는 class...를 사용하여 템플릿 매개변수 팩 선언
- Args...와 같이 매개변수 팩 확장
- sizeof...(args)로 매개변수 팩의 크기 조회
- 재귀적 템플릿 인스턴스화를 통한 매개변수 팩 처리
7.4.1 완벽한 전달(Perfect Forwarding)
C++11의 가변 템플릿과 함께 사용되는 강력한 기능인 완벽한 전달을 살펴보겠습니다:
#include <iostream>
#include <utility>
#include <string>
// 값을 받아 처리하는 함수들
void process(int& i) {
std::cout << "좌측값 참조 process(): " << i << std::endl;
}
void process(int&& i) {
std::cout << "우측값 참조 process(): " << i << std::endl;
}
void process(const std::string& s) {
std::cout << "상수 좌측값 참조 process(): " << s << std::endl;
}
void process(std::string&& s) {
std::cout << "우측값 참조 process(): " << s << std::endl;
}
// 완벽한 전달을 사용한 래퍼 함수
template <typename T>
void forwardingWrapper(T&& arg) {
std::cout << "래퍼 함수 호출, 인수 전달: ";
// std::forward를 사용하여 원래의 값 범주를 보존
process(std::forward<T>(arg));
}
// 가변 템플릿과 완벽한 전달을 결합
template <typename... Args>
void forwardingWrapperPack(Args&&... args) {
// 여러 인수를 각각 적절히 전달
// C++17의 fold expression을 사용하면 더 간단하게 가능
int dummy[] = { 0, (process(std::forward<Args>(args)), 0)... };
(void)dummy; // 미사용 변수 경고 방지
}
int main() {
int x = 42;
const std::string str = "Hello";
std::cout << "--- 단일 인수 전달 ---" << std::endl;
forwardingWrapper(x); // x는 좌측값
forwardingWrapper(100); // 100은 우측값
forwardingWrapper(str); // str은 상수 좌측값
forwardingWrapper(std::string("World")); // 임시 객체는 우측값
std::cout << "\n--- 가변 인수 전달 ---" << std::endl;
forwardingWrapperPack(x, 100, str, std::string("World"));
return 0;
}
완벽한 전달은 함수가 받은 인수를 그 값 범주(좌측값/우측값)를 유지한 채 다른 함수에 전달하는 기법입니다. 이를 위해 다음 두 가지가 필요합니다:
- 보편 참조(Universal Reference, T&&)를 사용한 매개변수
- std::forward<T>()를 사용한 값 범주 보존
이런 완벽한 전달은 래퍼 함수, 팩토리 함수, 컨테이너 요소 생성자 등 다양한 상황에서 유용합니다.