Sunday, May 8, 2011

C++0x and Variadic Templates

I recently looked into C++0x's variadic templates and thought that I should put up a couple of tips so that someone can get started with them.

The first thing is that with variadic templates, the only way to use them is in a recursive structure.
Now for an example:

void output() {
  std::cout << "\n";
}

template<class T, class... Args>
void output(T value, Args... args) {
  std::cout << value << "; ";
  output(args...);
}


There are a few things about this example you should notice. The declaration of the class template has the period of ellipses before the name of the parameter pack type, but the use of parameter pack as an argument always has the periods after the pack name (args...).

Then there is what I like to call the "Falling Tower Structure," which describes the functions which you have to define. You should define the maximum number of parameters you would need for the function with the variadic template, then you need to define the functions incrementally starting from the base case up to that function with an increasing number of parameters. I recommend using this structure when the number of parameters you need can vary because while there is probably more code, it is much cleaner than any other method I came up with.
Example time:

void test() { /*...*/ }

template<class T, class U>
void test(T t, U u) { /*...*/ }

template<class T, class U, class V>
void test(T t, U u, V v) { /*...*/ }

// Takes care of test with 3 or more parameters (args... can be empty)
template<class T, class U, class V, class... Args>
void test(T t, U u, V v, Args... args) { /*...*/ }

Here's another thing you might want to know if you want to use variadic templates as well. Say you have a situation where you are parsing through a list of arguments and if you get an indicator, you want to interpret an argument as a specific type. Here's an example of a rudimentary printf with support for specifying width argument:

#include <iostream>
#include <iomanip>
#include <string.h>

template<class T>
inline bitcopy(void *p, T* t) {
  memcpy(p, t, sizeof(*t));
}

void test(const char* s) {
  while (*s) {
    if (*s == '%' && *(++s) != '%') {
      throw std::runtime_error("Not enough parameters!");
    }
    std::cout << *s++;
  }
}

template<class T>
void test(const char* s, T t) {
  while (*s) {
    if (*s == '%' && *(++s) != '%') {
      if (*s == '*') {
        throw std::runtime_error("Not enough parameters!");
      } else {
        std::cout << t;
        test(++s);
      }
      return;
    }
    std::cout << *s++;
  }
  throw std::logic_error("Too many arguments!");
}

template<class T, class U, class... Args>
void test(const char* s, T t, U u, Args... args) {
  while (*s) {
    if (*s == '%' && *(++s) != '%') {
      if (*s == '*') {
        int width = 0;
//      width = t;                // Uncommenting this would cause a failure if the parameters aren't all int's.
        bitcopy(&width, &t);
        std::cout << std::setw(width) << u;
        test(s+2, args...);
      } else {
        std::cout << t;
        test(++s, u, args...);
      }
      return;
    }
    std::cout << *s++;
  }
  throw std::logic_error("Too many arguments!");
}

int main(int argc, char** argv) {
  test("Hello my name is %*s and I am %s.\n", 15, "Ashkan", 10);
  return 0;
}

Now here's something I made up so that I could work with a parameter by it's index. It might not be useful and it's really simple, but it should get you started on writing any utility functions you might need.

void getn(void* p, int n) {
}

template<class T, class... Args>
void getn(void* p, int n, T t, Args... args) {
  if (n == 0)
    bitcopy(p, &t);
  else
    getn(p, n-1, args...);
}

Last note: You can get the size of the parameter pack list by using sizeof.
Example:

sizeof...(args...)

No comments:

Post a Comment