프로그래밍 언어/C/C++

[C++11] Variadic template

소혼 2013. 11. 6. 20:17
반응형
목차로 가기


<개인적으로 공부한 내용들로, 내용이 완벽하지 않을 수 있습니다. 부족한 부분/틀린 부분에 대한 Comment 환영합니다.>



먼저 짧은 영어 탓에 variadic이라는 단어부터 찾아봤습니다.

하지만, variadic이란 단어는 원래 존재했던 단어는 아닌듯 합니다. (http://en.wiktionary.org/wiki/variadic)

어원은 variable 과 -adic 이 결합된 단어라고 하며, `임의의 많은 변수를 취하는` 이란 뜻입니다.


C언어를 접해서 쓰고 있는 사람이라면 printf를 생각하면 쉬울 듯 합니다.

printf는 대표적인  variadic function입니다.

### c++

// printf의 선언

int printf(const char *format, ...);


Variadic Template을 지원한다는 것은 결국 임의의 많은 template parameter를 취하는 템플릿을 지원한다는 것을 의미합니다.


기본적인 모양은 아래와 같습니다.

### c++

template<typename ... Values> class tuple;

(코딩 스타일은 어떻게 하는 것이 좋은지 모르겠네요. ... (ellipse operator) 과 typename, Values사이에는  space가 있어도 되고 없어도 되는듯 합니다.)


인스턴스를 만들때는 아래처럼 쓰게 됩니다.

### c++

// 세개의 template 인자를 갖는 some_instance_name

tuple<int, std::vector<int>, std::map<std::string, std::vector<int>>> some_instance_name;

//인자의 개수가 0개여도 괜찮습니다.

tuple<> a;


몇개의 인자를 제한하고자 하면 아래와 같이 해당하는 인자들뒤에 typename ... values 를 적으면 됩니다.

### c++

template<typename T, typename ... Values> class tuple;


간단한 예제를 만들어봤습니다.

### c++

#include <iostream>
using namespace std;

class dog {
public:
    void speak() const { cout << "bow wow" << endl; }
};
class cat {
public:
    void speak() const { cout << "meow" << endl; }
};

template <typename T>
void speak(const T& p)
{
    p.speak();
}

template <typename T, typename ... ARGS>
void speak(const T& p, ARGS ... args)
{  
    p.speak();
    speak(args...);
}

int main()
{
    dog a, b, c, d;
    cat e, f, g, h;
    speak(a, e, b, f, c, g, d, h);
    return 0;
}

좀 억지스러운 면이 있네요(굳이 이렇게 어렵게 짤 필요는 없어보입니다.).

적절한 사용법은 찾아야 될듯 합니다.(실제웹킷에서 쓰는 예를  아래에 첨부했습니다.)


어쨌든 30라인의 speak라는 함수는 dog객체와 cat객체를 임의의 개수만큼 받을 수 있도록 되어 있습니다.이를 위해 두가지 버전의 speak함수를 구현했습니다. (13-17라인, 19-24라인)

19-24라인의 speak는 variadic template으로 구현되어 있습니다. 이 버전의 speak를 보면, 첫번째 템플릿 인자는 T로 받아서(19라인) speak()를 호출합니다. (22라인), 그리고 나머지 인자들은 args... 이용해 speak를 다시 호출합니다. (23라인)

즉, 재귀적으로 variadic template 메소드를 호출하다가 마지막으로 인자가 한개만 남게 되면 13-17라인의 speak 함수를 통해 출력하게 됩니다. 이런 형태의 template을 recursive template이라고 합니다.


만약 13-17라인의 speak 함수가 없다면, 컴파일 타임에 아래와 같은 에러를 출력하게 됩니다.(gcc)

variadic_template.cpp: In instantiation of ‘void speak(const T&, ARGS ...) [with T = cat; ARGS = {}]’:
variadic_template.cpp:18:5:   recursively required from ‘void speak(const T&, ARGS ...) [with T = cat; ARGS = {dog, cat, dog, cat, dog, cat}]’
variadic_template.cpp:18:5:   required from ‘void speak(const T&, ARGS ...) [with T = dog; ARGS = {cat, dog, cat, dog, cat, dog, cat}]’
variadic_template.cpp:25:33:   required from here
variadic_template.cpp:18:5: error: no matching function for call to ‘speak()’
variadic_template.cpp:18:5: note: candidate is:
variadic_template.cpp:15:6: note: template<class T, class ... ARGS> void speak(const T&, ARGS ...)
variadic_template.cpp:15:6: note:   template argument deduction/substitution failed:
variadic_template.cpp:18:5: note:   candidate expects 2 arguments, 0 provided


유사한 예제로, Dog객체와 Cat 객체가 순서대로 있어야 하는 speak를 아래와 같이 만들수도 있습니다.

### c++

template <class Dog, class Cat>
void speak(const Dog& d, const Cat& c)
{
    d.speak();
}

template <class Dog, class Cat, class ... ARGS>
void speak(const Dog& d, const Cat& c, ARGS ... args)
{
    d.speak();
    speak(args...);
}

int main()
{
    dog a, b, c, d;
    cat e, f, g, h;
    speak(a, e, b, f, c, g, d, h);
    //speak(e, b, f, c, g, d, h); //Compile Error!
    return 0;
}

이렇게  일부 인자만을 지정하는 것을 partial specialization이라고 부르기도 합니다.



ellipse operator

보신 바와 같이, ellipse operator(...)는 크게 두가지 형태로 사용됩니다.

첫번째로, template parameter list나 speak()와 같이 template body의 인자 목록에서 사용될 경우 parameter pack을 선언한다고 합니다. (전자의 경우 template parameter pack, 후자의 경우 function parameter pack)

### c++

// template parameter pack
type ... Args
typename|class ... Args
template < parameter-list > ... Args

// function parameter pack
Args ... args

결국 parameter pack 을 가지는 template이 Variadic template이라고 할 수 있습니다.



두번째로, spack()의 body에서 사용된 것처럼 (args ... ) ellipse operator 가 템플릿 또는 함수 호출 인자의 오른쪽에 나오는 경우, parameter pack은 unpack한다 또는 확장(expansion)한다고 합니다.


위 예제에서는 간단한 형태의 parameter pack expansion 을 보여드렸습니다. (args ... )

하지만, expansion이 어디서 일어나느냐에 따라 다양한 형태로 이뤄지게 됩니다.


확장이 이뤄질 수 있는 곳들은 다음과 같습니다. (아래 참조에 있는 것들을 가져왔습니다.)

Function Argument list

### c++

f(&args...); // expands to f(&E1, &E2, &E3)
f(n, ++args...); // expands to f(n, ++E1, ++E2, ++E3);
f(++args..., n); // expands to f(++E1, ++E2, ++E3, n);
f(const_cast<const Args*>(&args)...);
// f(const_cast<const E1*>(&X1), const_cast<const E2*>(&X2), const_cast<const E3*>(&X#)
f(h(args...) + args...); // expands to
// f(h(E1,E2,E3) + E1, h(E1,E2,E3) + E2, h(E1,E2,E3) + E3)


Template Argument list

### c++

template<class A, class B, class...C> void func(A arg1, B arg2, C...arg3)
{
    container<A,B,C...> t1;  // expands to container<A,B,E1,E2,E3>
    container<C...,A,B> t2;  // expands to container<E1,E2,E3,A,B>
    container<A,C...,B> t3;  // expands to container<A,E1,E2,E3,B>
}


Template Parameter list

### c++

template<typename... T> struct value_holder
{
    template<T... Values> // expands to a non-type template parameter
    struct apply { };     // list, such as <int, char, int(&)[5]>
};


sizeof operator

### c++

template<class... Types>
struct count {
    static const std::size_t value = sizeof...(Types);
};

만약 count<int, char> 이면  value는 2가 될 것입니다.


Initializer list of array

### c++

template<typename... Ts> void func(Ts... args){
    const int size = sizeof...(args) + 2;
    int res[size] = {1,args...,2};
}


Member initializer list

### c++

template<class... Mixins>
class X : public Mixins... {
 public:
    X(const Mixins&... mixins) : Mixins(mixins)... { }
};


### c++

#include <iostream>
using namespace std;

struct a1{};
struct a2{};
struct a3{};
struct a4{};

template<class X> struct baseC{
    baseC(int a) {printf("baseC primary ctor: %d\n", a);}
};
template<> struct baseC<a1>{
    baseC(int a) {printf("baseC a1 ctor: %d\n", a);}
};
template<> struct baseC<a2>{
    baseC(int a) {printf("baseC a2 ctor: %d\n", a);}
};
template<> struct baseC<a3>{
    baseC(int a) {printf("baseC a3 ctor: %d\n", a);}
};
template<> struct baseC<a4>{
    baseC(int a) {printf("baseC a4 ctor: %d\n", a);}
};

template<class...A> struct container : public baseC<A>...{
    container(): baseC<A>(12)...{
        printf("container ctor\n");
    }
};

int main(void){
    container<a1,a2,a3,a4> test;
    return 0;
}


lamda captures

### c++

template<class ...Args>
void f(Args... args) {
    auto lm = [&, args...] { return g(args...); };
    lm();
}


Exception specification list

### c++

template<class...X> void func(int arg) throw(X...)
{
 // ... throw different Xs in different situations
}




아래 link는 webkit에서 아직 공식 지원되지 않는 make_unique를 내부적으로 만들어서 쓰기 위해 variadic template 버전과 variadic template이 지원되지 않는 버전을 같이 만든 패치입니다.

http://trac.webkit.org/changeset/156000


참조: http://en.wikipedia.org/wiki/Variadic_templates

참조2 : http://en.cppreference.com/w/cpp/language/parameter_pack

참조3 : http://pic.dhe.ibm.com/infocenter/lnxpcomp/v111v131/index.jsp?topic=%2Fcom.ibm.xlcpp111.linux.doc%2Flanguage_ref%2Fvariadic_templates.html

반응형

'프로그래밍 언어 > C/C++' 카테고리의 다른 글

[C++11] Move semantics  (5) 2013.11.10
[c++11] rvalue reference  (1) 2013.11.07
[C++11] unique_ptr  (7) 2013.11.01
[C++11] Type Inference: auto  (4) 2013.11.01
c++11 스터디(목차)  (0) 2013.11.01