C++ 가변 인자 템플릿 고급 | pack 확장과 fold 표현식

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;
}

동작 원리:

  1. print(1, 2.0, "three"): T = int, Rest = {double, const char*}
  2. std::cout << 1, 그리고 print(2.0, "three") 호출
  3. print(2.0, "three"): T = double, Rest = {const char*}
  4. std::cout << 2.0, 그리고 print("three") 호출
  5. print("three"): T = const char*, Rest = {} (빈 pack)
  6. 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 + ...)단항 우측 folda1 + (a2 + a3)
(... + args)단항 좌측 fold(a1 + a2) + a3
(init + ... + args)이항 foldinit + 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 packtypename... 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와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.