Написание конструктора вариационных шаблонов
Недавно я спросил this вопрос, но теперь я хотел бы его расширить. Я написал следующий класс:
template <class T>
class X{
public:
vector<T> v;
template <class T>
X(T n) {
v.push_back(n);
}
template <class T, class... T2>
X(T n, T2... rest) {
v.push_back(n);
X(rest...);
}
};
При создании объекта с помощью
X<int> obj(1, 2, 3); // obj.v containts only 1
Вектор содержит только первое значение, но не другие. Я проверил и увидел, что конструктор называется 3 раза, поэтому я, вероятно, создаю временные объекты и заполняя их векторы остальными аргументами. Как решить эту проблему?
Ответы
Ответ 1
Во-первых, ваш код не компилируется для меня.
main.cpp:7:15: error: declaration of ‘class T’
template <class T>
^
main.cpp:3:11: error: shadows template parm ‘class T’
template <class T>
^
Я изменил внешний на U
.
template <class U>
class X{
public:
vector<U> v;
template <class T>
X(T n) {
v.push_back(n);
}
template <class T, class... T2>
X(T n, T2... rest) {
v.push_back(n);
X(rest...);
}
};
Вы правы, что это вызывает проблему, которую вы задали в деталях вопроса...
X<int> obj(1, 2, 3); // obj.v containts only 1
Это потому, что оператор X(rest...)
в конце вашего конструктора не рекурсивно вызывает конструктор для продолжения инициализации одного и того же объекта; он создает новый объект X
, а затем отбрасывает его. Как только тело конструктора начинает выполняться, более невозможно вызывать другой конструктор на том же объекте. Делегирование должно выполняться в ctor-инициализаторе. Например, вы можете сделать это:
template <class T, class... T2>
X(T n, T2... rest): X(rest...) {
v.insert(v.begin(), n);
}
Это отстой, хотя вставка в начале вектора неэффективна.
Лучше взять аргумент std::initializer_list<T>
. Это то, что делает сам std::vector
.
X(std::initializer_list<U> il): v(il) {}
// ...
X<int> obj {1, 2, 3};
Ответ 2
Полностью согласен с ответом Брайана, но даже если правильный подход - другой (т.е. использовать initializer_list), будьте внимательны (ради этого правильно в других обстоятельствах), что рекурсия вариационного шаблона в этом случае может быть намного проще, отметив, что пустой пакет является допустимым пакетом параметров шаблона, так что конструктор вариационных шаблонов будет называться в последний раз с пустым пакетом, что приведет к попытке вызвать по умолчанию ctor, который просто может быть отменен по умолчанию и прекратит рекурсию.
IOW, следующее будет работать и будет намного чище IMO:
template <class T>
struct X
{
std::vector<T> v;
X() = default; //Terminating recursion
template <class U, class... Ts>
X(U n, Ts... rest) : X(rest...) { .. the recursive work ..}
};
Опять же, я не говорю, что шаблоны и рекурсия здесь правильные, я просто указываю, что это было бы необходимо, был бы более простой способ их использования.
Ответ 3
В первую очередь нет необходимости в рекурсии -
вы можете использовать идиому "временного массива" и написать
template <class... E>
X(E&&... e) {
int temp[] = {(v.push_back(e), 0)...};
}
Собственная (и более сложная) версия этого подхода выглядит так:
template <class... E>
X(E&&... e) {
(void)std::initializer_list<int>{(v.push_back(std::forward<E>(e)), void(), 0)...};
}
Текущая версия
Обратите внимание, что следующая версия С++, вероятно, имеет Fold Expressions
(v.push_back(e), ...);
Ответ 4
template <class T, class... Rest>
X(T n, Rest... rest) {
add(rest...);
}
//Just another member function
template<class T>
void add(T n) {
v.push_back(n);
}
template<class T, class ... Rest>
void add(T n, Rest... rest) {
v.push_back(n);
add(rest...);
}