Variadic Template 기본

개념

  • Variadic Template템플릿 타입 인자의 개수에 제한이 없는 템플릿을 작성할 수 있는 기능
  • 즉, 인자의 개수를 가변적으로 받아들이는 템플릿
  template<typename ... Ts> class tuple;

template<typename ...Ts>
void f2(Ts ... args);

void f2(const Ts& ... args);
  

위처럼 ... 문법을 통해 타입과 함수 인자 모두 가변적으로 선언 가능.

여러 타입을 받는 tuple이 이를 활용

Pack Expansion

용어

  • Ts: template parameter pack
  • args: function parameter pack
  • sizeof...: pack 안 요소의 개수
  template<typename ... Ts>
void fn(const Ts& ... args);
  

... 연산자는 앞에 있는 요소를 확장한다. 괄호로 묶으면 전체에 적용된다.

예제

예를 들어, 여러 인자를 함수에 전달하거나 각 인자에 연산을 적용할 수 있다.

  
template<typename ... Ts>
void fn(Ts ... args) {
    foo(args...);             // foo(1, 2, -3)
    foo((++args)...);         // foo(++1, ++2, ++-3) → foo(2, 3, -2)
    foo(abs(args)...);        // foo(abs(2), abs(3), abs(-2))
}
  
  • args... → 인자들을 순서대로 펼쳐서 전달
  • (++args)... → 각 인자에 ++ 연산 후 전달
  • abs(args)... → 각 인자에 abs() 적용 후 전달

예제 2

다음과 같이 템플릿 파라미터 팩값 팩을 동시에 사용한 예제도 가능

  template<typename ... Ts>
struct Outer {
    template<Ts ... value>
    struct Inner {};
};

Outer<int, double>::Inner<3, 3.4> in;
  

예제 3: 다중 상속에 활용

가변 템플릿을 사용하면 다중 상속도 간결하게 표현 가능

  class A {};
class B {};

template<typename... Ts>
class X : public Ts... {
public:
    X(const Ts&... args) : Ts(args)... {}
};

int main() {
    A a; B b;
    X<A, B> x(a, b);
}
  
  • X<A, B>class X : public A, public B로 해석
  • 생성자도 A(a), B(b) 형태로 확장됨

Recursion (재귀)

Pack Expansion 없이 각 인자에 접근하려면 재귀적 구조를 사용

  template<typename T, typename ... Ts>
void print(T value, Ts ... args) {
    std::cout << value << " ";
    if constexpr (sizeof...(args) > 0)
        print(args...);
}
  

마지막 인자가 없는 버전을 위해 if constexpr로 분기

sum 만들기

recursion 방식

재귀 방식으로 인자들을 더하는 함수:

  int sum() { return 0; }

template<typename T, typename ... Ts>
auto sum(T value, Ts ... args) {
    return value + sum(args...);
}
  

Fold Expression 방식(C++17)

C++17부터는 fold expression을 사용해 더 간결하게 작성 가능

  template<typename ... Ts>
auto sum(Ts ... args) {
    auto s1 = (args + ...);        // 오른쪽 fold
    auto s2 = (... + args);        // 왼쪽 fold
    auto s3 = (args + ... + 0);    // 초기값 포함 fold
    auto s4 = (0 + ... + args);    // 초기값 포함 fold
    return s1;
}
  

Fold Expression 더 알아보기

출력 함수 예제 (fold)

  template<typename ... Ts>
void show(Ts ... args) {
    (std::print("{} ", args), ...);
}
  

std::print를 사용해 인자들을 출력합니다.

vector에 push (fold)

  template<typename T, typename ... Ts>
void push_vec(std::vector<T>& v, Ts&& ... args) {
    (v.push_back(std::forward<Ts>(args)), ...);
}
  

vector template 예제

  void f1(int a, int b) { std::this_thread::sleep_for(2s); }
void f2(double d) { std::this_thread::sleep_for(3s); }

template<typename F, typename ... Ts>
decltype(auto) chronometry(F&& f, Ts&& ... args)
{
    stopwatch sw;
    return std::invoke( std::forward<F>(f), std::forward<Ts> (args)...);
}

int main()
{
    chronometry(f1, 5, 6);
    chronometry(f2, 3.4);
}