BOOST Code Reading - Variadic Template

When I was learning JavaScript at winter vacation, I just found that in JavaScript, we can just write functions like this

function sumAll(...args) { // args is the name for the array
  let sum = 0;

  for (let arg of args) sum += arg;

  return sum;
}

alert( sumAll(1) ); // 1
alert( sumAll(1, 2) ); // 3
alert( sumAll(1, 2, 3) ); // 6

As you can see here, we can just write ellipisis in the parameter field and retrive all of the parameter that user provide. It’s very elegant isn’t it ? At that time, I think that it’s not a work that can be done in C++. However, we can almost do the same and even more better in C++ by Variadic Template.

In fact, this job can be finished in C-style ellipsis operator. But as far as I know, it’s just some marco and can’t be finished at compile time. Okey, let’s stop talk about bull shit and introducing Variadic Template.

Basic Usage

Firstly, we try to implement the same function as the JavaScript example above.

template<typename T>
T sumAll(T first) {
    return first;
}

template<typename T, typename ...Ts>
T sumAll(T first, Ts... ts) {
    return first + sumAll(ts...);
}

Clearly, the second function is the key. We can use typename …Tsto pack all of the parameter in one typename and use it in my function. This syntax is intuitively. You can use…Tsto pack data in one bundle andts…` to unpack a bundle.

In this function, you can pass as much parameter as you want.

    std::cout << sumAll(1, 2, 3) << std::endl; // 6
    std::string s1 = "cydiater";
    std::string s2 = " is ";
    std::string s3 = "rubbish";
    std::cout << sumAll(s1, s2, s3) << std::endl; // cydiater is rubbish

For some readers it may seems that this is accomplished by recursive. But actually, due to the compiler optimaztion. The running is efficiency.

std::tuple

You can better understand how Variadic Template can be used in real code by demostrating how std::tuple is implemented in Standerd Template Library.

The first thing we need to handle is to pass arbitary template paramter to generate a tuple. Using class inheritance, we can write the code like this

template<typename ...T>
struct pack { };

template<typename T, typename ...Ts> 
struct pack<T, Ts...> : pack<Ts...> {
    pack(T t, Ts... ts) : pack<Ts...>(ts...), tail(t) { };
    T tail;
};

Then we can use it

pack<int, int, std::string, double> data(1, 2, "cydiater", 12.0);

But there is a question remained, how can we get data from it? In STL, you can use get<2>(data) to get"cydiater". Here we need to consider how to imitate this function.

Let’s just think about how to get the type in advance. This is the first step.

Using template specialization, we can accomplish it as follows

template<size_t, typename> struct lookup_one_type{ };

template<typename T, typename ...Ts>
struct lookup_one_type<0, pack<T, Ts...>> {
    typedef T type;
};

template<size_t k, typename T, typename ...Ts>
struct lookup_one_type<k, pack<T, Ts...>> {
    typedef typename lookup_one_type<k - 1, pack<Ts...>>::type type;
};

Obviously, when the first parameter of lookup_one_type is 0, all we want it just the first parameter of pack, so we use it as our type. When it is not 0, we can lookup type from pack<Ts...>. Since we pass pack<T, Ts...> as parameter, we can unwrap it by remove T.

Finally, we have our own get function

template<size_t k, typename ...Ts>
typename std::enable_if<k == 0, typename lookup_one_type<0, pack<Ts...>>::type&>::type
get(pack<Ts...> &tmp) {
    return tmp.tail;
}

template<size_t k, typename T, typename ...Ts>
typename std::enable_if<k != 0, typename lookup_one_type<k, pack<T, Ts...>>::type&>::type
get(pack<T, Ts...> &tmp) {
    pack<Ts...> &base = tmp;
    return get<k - 1>(base);
}

But wait, what is std::enable_if and why do we need to use it?

To be short, std::enable_if<true, T>::type is T and std::enable_if<false, T> is a totally empty class. Just think of that if we don’t use it, how can we write get<0>? Since 0 is already known in this scope and we don’t need to pass 0, it must seem unelegant.

And here we get all we want

    pack<int, int, std::string, double> data(1, 2, "cydiater", 12.0);
    std::cout << get<2>(data) << std::endl; // cydiater