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).
精彩评论