C++ 가변 인자 템플릿 고급 | pack 확장과 fold 표현식
이 글의 핵심
C++ 가변 인자 템플릿 고급에 대한 실전 가이드입니다. pack 확장과 fold 표현식 등을 예제와 함께 상세히 설명합니다.
가변 인자 템플릿 고급
가변 인자 템플릿(variadic template) 은 타입·인자 개수가 가변인 템플릿입니다. ...Args 로 패킹하고, 재귀나 fold 표현식(C++17) 으로 풀어서 처리합니다. 템플릿 기초와 템플릿 특수화를 알고 있으면 응용하기 좋습니다.
패킹과 재귀
가변 인자를 받으려면 parameter pack을 선언하고, 재귀 종료 버전(인자 0개)과 재귀 버전(첫 인자 + 나머지 pack)을 둡니다.
#include <iostream>
// 재귀 종료 (base case)
void print() {
std::cout << '\n';
}
// 재귀 버전
template<typename T, typename... Rest>
void print(T first, Rest... rest) {
std::cout << first << ' ';
print(rest...); // 나머지 pack 풀어서 재귀 호출
}
int main() {
print(1, 2.0, "three");
// 출력: 1 2 three
return 0;
}
동작 원리:
print(1, 2.0, "three"):T = int,Rest = {double, const char*}std::cout << 1, 그리고print(2.0, "three")호출print(2.0, "three"):T = double,Rest = {const char*}std::cout << 2.0, 그리고print("three")호출print("three"):T = const char*,Rest = {}(빈 pack)std::cout << "three", 그리고print()호출 (base case)
실무 팁: Rest...는 “나머지 타입들”, rest...는 “나머지 인자들”을 풀어서 다음 print 호출에 넘깁니다. fold 표현식을 쓰면 이런 재귀를 한 줄로 줄일 수 있습니다.
// C++17 fold 표현식으로 간결하게
template<typename... Args>
void print_fold(Args... args) {
(std::cout << ... << args) << '\n';
}
print_fold(1, 2.0, "three"); // 재귀 없이 한 줄
Fold 표현식 (C++17)
Fold는 pack 전체에 이항 연산자를 적용합니다. (args + ...)는 단항 우측 fold로, args_1 + (args_2 + (args_3 + ...))와 같습니다.
template<typename... Args>
auto sum(Args... args) {
return (args + ...); // 단항 우측 fold
}
template<typename... Args>
bool all(Args... args) {
return (args && ...);
}
// 빈 pack일 때 초기값을 주려면 이항 fold
template<typename... Args>
auto sum_with_zero(Args... args) {
return (0 + ... + args); // 빈 pack이면 0
}
실무에서는 로깅, 유틸리티 래퍼, 타입 리스트 처리 등에 자주 씁니다.
Pack 확장 패턴
pack을 확장할 때 (expr)... 형태로 쓰면 pack의 각 원소마다 expr이 반복됩니다.
#include <iostream>
#include <vector>
// 패턴 1: 함수 호출에 pack 확장
template<typename... Args>
void call_functions(Args... args) {
(process(args), ...); // process(arg1), process(arg2), ...
}
// 패턴 2: 벡터에 pack 추가
template<typename... Args>
void add_to_vector(std::vector<int>& v, Args... args) {
(v.push_back(args), ...); // v.push_back(arg1), v.push_back(arg2), ...
}
// 패턴 3: 출력 fold
template<typename... Args>
void log(Args&&... args) {
(std::cout << ... << args) << '\n'; // arg1 << arg2 << arg3 << ...
}
int main() {
std::vector<int> v;
add_to_vector(v, 1, 2, 3, 4, 5);
log("Values: ", 10, ", ", 20, ", ", 30);
// 출력: Values: 10, 20, 30
}
확장 패턴 종류:
| 패턴 | 의미 | 예시 |
|---|---|---|
args... | pack 풀기 | f(args...) → f(a1, a2, a3) |
(expr(args))... | 각 원소에 expr 적용 | (f(args))... → f(a1), f(a2), f(a3) |
(args + ...) | 단항 우측 fold | a1 + (a2 + a3) |
(... + args) | 단항 좌측 fold | (a1 + a2) + a3 |
(init + ... + args) | 이항 fold | init + a1 + a2 + a3 |
실무 활용:
// 실무 예시 1: 여러 값을 한 번에 설정
template<typename... Args>
void set_values(int& result, Args... args) {
result = (args + ...); // 모든 인자의 합
}
// 실무 예시 2: 여러 조건 AND
template<typename... Args>
bool all_true(Args... args) {
return (args && ...); // 모든 인자가 true인지
}
// 실무 예시 3: 여러 함수 호출
template<typename... Funcs>
void execute_all(Funcs... funcs) {
(funcs(), ...); // 모든 함수 순차 호출
}
실전 예시: 타입 안전한 튜플 접근
가변 인자로 타입 목록을 받아, 인덱스에 해당하는 타입을 반환하는 메타 함수를 만들 수 있습니다. structured binding과 함께 쓰면 편합니다.
template<typename... Ts>
struct type_list {};
template<size_t I, typename T>
struct type_at_impl;
template<typename T, typename... Rest>
struct type_at_impl<0, type_list<T, Rest...>> {
using type = T;
};
template<size_t I, typename T, typename... Rest>
struct type_at_impl<I, type_list<T, Rest...>> {
using type = typename type_at_impl<I - 1, type_list<Rest...>>::type;
};
관련 글
- 템플릿 템플릿 인자: 템플릿을 인자로 받는 문법
- auto 타입 추론: 반환형·변수에서 타입 생략
자주 발생하는 문제
- 빈 pack: fold에서 빈 pack이면 일부 연산자는 잘못된 결과가 됩니다. 이항 fold로 초기값을 주거나,
if constexpr (sizeof...(args) > 0)로 분기하세요. - pack 확장 위치:
(args)...와(args...)는 다릅니다. 전자는 “각 args에 대해 괄호 안 식”, 후자는 “pack 전체를 한 괄호로”. 문맥에 맞게 사용하세요.
정리
| 항목 | 설명 |
|---|---|
| parameter pack | typename... Args, Args... args 로 선언·사용 |
| fold | (args op ...), (... op args), (init op ... op args) 등으로 pack 한 번에 연산 |
| 실무 | 로깅, 유틸 래퍼, 타입 리스트, 가변 인자 래퍼에 활용 |
한 줄 요약: 가변 인자 템플릿으로 인자 개수에 구애받지 않는 함수·클래스를 만들고, C++17 fold로 재귀 없이 처리할 수 있습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ 가변 인자 템플릿 | Variadic Templates와 Fold Expression
- C++ Fold Expressions | “파라미터 팩 접기” 가이드
- C++ 템플릿 | “제네릭 프로그래밍” 초보자 가이드
- C++ 템플릿 특수화 | template specialization 가이드
- C++ 템플릿 템플릿 인자 | template template parameter 가이드
이 글에서 다루는 키워드 (관련 검색어)
C++, variadic template, parameter pack, fold expression, C++17 등으로 검색하시면 이 글이 도움이 됩니다.
실전 체크리스트
실무에서 이 개념을 적용할 때 확인해야 할 사항입니다.
코드 작성 전
- 이 기법이 현재 문제를 해결하는 최선의 방법인가?
- 팀원들이 이 코드를 이해하고 유지보수할 수 있는가?
- 성능 요구사항을 만족하는가?
코드 작성 중
- 컴파일러 경고를 모두 해결했는가?
- 엣지 케이스를 고려했는가?
- 에러 처리가 적절한가?
코드 리뷰 시
- 코드의 의도가 명확한가?
- 테스트 케이스가 충분한가?
- 문서화가 되어 있는가?
이 체크리스트를 활용하여 실수를 줄이고 코드 품질을 높이세요.
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. C++ 가변 인자 템플릿 고급. parameter pack, pack 확장, fold expression(C++17)으로 가변 인자 함수·타입을 다루는 방법. 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.