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

완벽한 전달은 함수가 받은 인수를 그 값 범주(좌측값/우측값)를 유지한 채 다른 함수에 전달하는 기법입니다. 이를 위해 다음 두 가지가 필요합니다:

  1. 보편 참조(Universal Reference, T&&)를 사용한 매개변수
  2. std::forward<T>()를 사용한 값 범주 보존

이런 완벽한 전달은 래퍼 함수, 팩토리 함수, 컨테이너 요소 생성자 등 다양한 상황에서 유용합니다.