开发者

Variadic templates and new

I have this class template:

template<class... T>
class Test {
  std::vector<TestCase*> test_cases;
public:
  Test() {
    // Here, for each T an instance should be added to test_cases.
    test_cases.push_back((new T)...);
  }
};

This works fine for one template argument, but for multiple arguments I get this error:

error: too many arguments to function call, expected 1, have 2

How can I use variadic templates with new this way? What is the correct syntax?

开发者_如何转开发

EDIT: I think my question wasn't quite clear. What I want is this:

Test<TestCase1, TestCase2, TestCase3>;
// The constructor will then be:
test_cases.push_back(new TestCase1);
test_cases.push_back(new TestCase2);
test_cases.push_back(new TestCase3);

My compiler is clang 163.7.1 with this flag: -std=c++0x.


vector::push_back expects one parameter so you can't expand the variadic template in the function call. Also I added a template parameter for the base class (from which all other classes derive).

Here's something that compiles.

struct base{};
struct d0 : base{};
struct d1 : base{};
struct d2 : base{};

#include <vector>

// termination condition for helper function
template <class T>
void add(std::vector<T*>&) { 
}

// helper function
template <class T, class Head, class... Tail>
void add(std::vector<T*>& v) { 
       v.push_back(new Head()); 
       add<T, Tail...>(v);
}

template <class T, class ... U>
class test
{
    std::vector<T*> vec;
public:
    test() {
       add<T, U...>(vec);      
    }
};

int main() 
{
    test<base, d0,d1,d2> t;
}


You can accomplish this, but it’s a bit roundabout since you write the expression directly. You need to call push_back once for each argument in the variadic template argument list.

How do you achieve this? Well, by calling a recursive function once for each template argument:

template <typename Base, typename T1, typename T2, typename... T>
void fill(std::vector<Base*>& vec) {
    vec.push_back(new T1);
    fill<Base, T2, T...>(vec);
}

template <typename Base, typename T1>
void fill(std::vector<Base*>& vec) {
    vec.push_back(new T1);
}

Here we have two overloads of the fill function, one with a variadic template argument list and one without – this is the recursion base case. As long as there are still at least two template arguments, the first version gets called. If there is only a single argument left, the second argument is called instead.

Call it like this in the constructor:

fill<TestCase, T...>(test_cases);


Pack expansion can only happen in a select number of situations and doesn't work for arbitrary expressions or statements. However, since one of those situation is list-initialization and since the order of operations is defined for the brace initializers of list-initialization syntax, it's always possible to expand an arbitrary statement. To wit:

typedef std::initializer_list<int> expand;
expand { ( test_cases.push_back(new T), void(), 0 )... };

The void() trick is to suppress any invocation of an overloaded operator,. Completely irrelevant here but I have included it since it may be useful when refactoring the functionality in a macro:

#define EXPAND( exp ) \
    std::initializer_list<int> { ( (exp), void(), 0 )... }

// No use of '...', it's in the macro body
EXPAND(( test_cases.push_back(new T) ));


On a related note, in this particular case you can use vector's initializer_list support by writing the constructor as follows

Test()
:test_cases{ new T ... }
{ }

Or by using assignment if for any reason you can't use constructor initializers

Test() {
  test_cases = { new T ... };
}


Maybe you want a tuple inside your std::vector? Not sure if this is what you intended, but this compiles at least on my G++ 4.6.1 :D

#include <vector>
#include <utility>
#include <functional>
#include <string>

template<class... T>
class Test {
  std::vector<std::tuple<T*...>> test_cases;
public:
  Test() {
    // Here, for each T an instance should be added to test_cases.
    test_cases.push_back(std::tuple<T*...>((new T)...));
  }
};

int main()
{
   Test<int, float> foo;
   Test<std::string, double> bar;
}


It strikes me you want a dynamic vector of any type (although not personally looking myself, I was told by a friend there was apparently something like this in the boost library), as opposed to a template vector.

A template vector is basically a vector that can assume any of one defined type (either all ints, or all doubles, or all floats but not ints and doubles and floats).

The reason there isn't a class like this conventionally is because each item takes up a different block size in memory (a char is a byte, an int could be 4 bytes etc etc), and it would take additional resources on look-up to know what to expect (normal storage is contiguous... which a vector is, given it is 'basically' an array).

If you're looking to build your own (I tried), you're looking at void * pointers, dynamic memory allocation and a whole host of headaches involving typecasting (I am unaware of any automated method to correctly typecast an item behind the scenes, but others might be able to chip in).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜