Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Writing variadic template constructor

Recently I asked this question but now I would like to expand it. I wrote the following class:

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

When creating an object using

X<int> obj(1, 2, 3);  // obj.v containts only 1

Vector only contains the first value, but not others. I've checked and saw that constructor is called 3 times, so I'm probably creating temp objects and filling their vectors with the rest of the arguments. How do I solve this problem?

like image 322
Tracer Avatar asked Mar 04 '15 22:03

Tracer


4 Answers

First, your code doesn't compile for me.

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>
           ^

I changed the outer one to 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...);
    }
};

You're correct that this causes the issue you gave in the question details...

X<int> obj(1, 2, 3);  // obj.v containts only 1

This is because the statement X(rest...) at the end of your constructor doesn't recursively call the constructor to continue initializing the same object; it creates a new X object and then throws it away. Once a constructor's body begins to execute, it's no longer possible to invoke another constructor on the same object. Delegation must occur in the ctor-initializer. So for example, you could do this:

template <class T, class... T2>
X(T n, T2... rest): X(rest...) {
    v.insert(v.begin(), n);
}

That sucks though, because inserting at the beginning of a vector isn't efficient.

Better to take a std::initializer_list<T> argument. This is what std::vector itself does.

X(std::initializer_list<U> il): v(il) {}
// ...
X<int> obj {1, 2, 3};
like image 103
Brian Bi Avatar answered Nov 15 '22 10:11

Brian Bi


Totally agree with Brian's answer, but even though the right approach is another (i.e. use initializer_list) please be aware (for the sake of doing this correctly in other circumstances) that variadic template recursion in this case could be much simpler just by noting that an empty pack is a valid template parameter pack, so that the variadic template constructor will be called one final time with the empty pack, which will lead to trying to call a default ctor, which just can be defaulted and terminate the recursion.

IOW, the following would work and would be much cleaner 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 ..}
};

Again, I'm not saying templates and recursion are the right thing here, I'm just pointing out that would they be necessary, there would be a simpler way to use them.

like image 33
abigagli Avatar answered Nov 15 '22 10:11

abigagli


There is no need for recursion in the first place -
you can use the "temporary array" idiom and write

template <class... E>
X(E&&... e) {
    int temp[] = {(v.push_back(e), 0)...};
}

The proper (and more complicated) version of this approach looks like this:

template <class... E>
X(E&&... e) {
    (void)std::initializer_list<int>{(v.push_back(std::forward<E>(e)), void(), 0)...};
}

Live version

Note, than the next version of C++ will probably have the Fold Expressions
(v.push_back(e), ...);

like image 26
Abyx Avatar answered Nov 15 '22 09:11

Abyx


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

 }
like image 40
fengzi Avatar answered Nov 15 '22 09:11

fengzi