Написание конструктора вариационных шаблонов

Недавно я спросил 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...);

 }