Шаблоны Variadic и новые

У меня есть этот шаблон класса:

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)...);
  }
};

Это отлично работает для одного аргумента шаблона, но для нескольких аргументов я получаю эту ошибку:

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

Как я могу использовать вариативные шаблоны с new таким образом? Каков правильный синтаксис?


РЕДАКТИРОВАТЬ: я думаю, что мой вопрос был не совсем ясен. Я хочу вот что:

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);

Мой компилятор clang 163.7.1 с этим флагом: -std=c++0x.


person Community    schedule 18.09.2011    source источник
comment
std::vector<T*> test_cases; выглядит странно, так как T содержит более одного типа.   -  person Flexo    schedule 18.09.2011
comment
@awoodland ты прав. Спасибо, что заметили это. На самом деле каждый T является подклассом TestCase (поэтому я использую указатели). Я изменю это.   -  person    schedule 18.09.2011
comment
Вы можете попробовать test_cases.push_back(new T())...;.   -  person Kerrek SB    schedule 18.09.2011
comment
@Kerrer SB: когда я это делаю, я получаю эту ошибку: ожидается ';' после выражения с примечанием: выражение содержит нерасширенный пакет параметров 'T'.   -  person    schedule 18.09.2011
comment
@WTP Ваш недавно добавленный пример на самом деле довольно плох, потому что он не будет работать (или только при использовании vector<void*>): int, double и char не имеют общего базового класса.   -  person Konrad Rudolph    schedule 18.09.2011
comment
Я бы предложил изменить заголовок вопроса на что-то вроде «Итерация переменных параметров шаблона».   -  person bitmask    schedule 19.09.2011


Ответы (6)


vector::push_back ожидает один параметр, поэтому вы не можете расширить вариативный шаблон в вызове функции. Также я добавил параметр шаблона для базового класса (от которого происходят все остальные классы).

Вот что компилируется.

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;
}
person Motti    schedule 18.09.2011

Вы можете сделать это, но это будет немного окольным путем, поскольку вы пишете выражение напрямую. Вам нужно вызвать push_back один раз для каждого аргумента в списке аргументов шаблона с переменным числом аргументов.

Как вы этого добиваетесь? Ну, вызвав рекурсивную функцию один раз для каждого аргумента шаблона:

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);
}

Здесь у нас есть две перегрузки функции fill, одна с вариативным списком аргументов шаблона, а другая без — это базовый случай рекурсии. Пока есть хотя бы два аргумента шаблона, вызывается первая версия. Если остался только один аргумент, вместо него вызывается второй аргумент.

Назовите это так в конструкторе:

fill<TestCase, T...>(test_cases);
person Konrad Rudolph    schedule 18.09.2011
comment
С положительной стороны, вы можете ожидать, что это будет оптимизировано компилятором, не так ли? - person bitmask; 19.09.2011
comment
@bitmask Я не уверен, что вы подразумеваете под «оптимизированным». Поскольку шаблоны полностью разрешаются во время компиляции, это будет полностью разрешено во время компиляции. Если вы имеете в виду инлайнинг, то да, «рекурсивные» вызовы fill будут полностью инлайнированы. - person Konrad Rudolph; 19.09.2011

Расширение пакета может произойти только в выбранном числе ситуаций и не работает для произвольных выражений или инструкций. Однако, поскольку одной из таких ситуаций является инициализация списка и поскольку порядок операций определен для инициализаторов фигурных скобок синтаксиса инициализации списка, всегда можно расширить произвольный оператор. А именно:

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

Хитрость void() состоит в том, чтобы подавить любой вызов перегруженного operator,. Совершенно неуместно здесь, но я включил его, так как он может быть полезен при рефакторинге функциональности макроса:

#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) ));
person Luc Danton    schedule 18.09.2011
comment
Смешной. Я написал ответ, который был почти идентичен этому, до сих пор не замечая, что он у вас уже был (я изменил свой ответ, чтобы больше не повторять то, что вы сказали :)). Но вы забыли скобки вокруг (exp), void(), 0 . Как и сейчас, попытка расширить литерал 0 приведет к ошибке. - person Johannes Schaub - litb; 19.09.2011

В соответствующей заметке в этом конкретном случае вы можете использовать поддержку вектора initializer_list, написав конструктор следующим образом

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

Или с помощью присваивания, если по какой-либо причине вы не можете использовать инициализаторы конструктора

Test() {
  test_cases = { new T ... };
}
person Johannes Schaub - litb    schedule 18.09.2011

Может быть, вам нужен кортеж внутри вашего std::vector? Не уверен, что это то, что вы хотели, но это компилируется по крайней мере на моем 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;
}
person Maister    schedule 18.09.2011
comment
Да, хорошая мысль... Исправил. Возился со способами компиляции и не подумал об очевидном :) - person Maister; 18.09.2011

Мне кажется, вам нужен динамический вектор типа любой (хотя лично я не смотрел, друг сказал мне, что в библиотеке повышения, по-видимому, есть что-то подобное), а не вектор шаблона.

Вектор-шаблон — это, по сути, вектор, который может принимать любой из одного определенного типа (либо все целые, либо все двойные, либо все числа с плавающей запятой, но не целые числа, двойные числа и числа с плавающей запятой).

Причина, по которой такого класса обычно нет, заключается в том, что каждый элемент занимает в памяти различный размер блока (char - это байт, int может быть 4 байта и т. д. и т. д.), и это потребует дополнительных ресурсов при поиске чтобы знать, чего ожидать (обычное хранилище является непрерывным... что такое вектор, учитывая, что это «в основном» массив).

Если вы хотите создать свой собственный (я пытался), вы смотрите на указатели void *, динамическое выделение памяти и целый ряд головных болей, связанных с приведением типов (я не знаю ни одного автоматизированного метода для правильного приведения типа элемента за кулисами , но и другие могут скинуться).

person SSight3    schedule 18.09.2011
comment
То, что хочет ОП, вполне возможно. Обратите внимание, что он явно использует вектор указателей. Это позволяет иметь место полиморфизму, если все типы имеют общий базовый класс (TestCase в его примере). - person Konrad Rudolph; 18.09.2011