Recently I asked this question but now I would like to expand it. I wrote the following class:
template
class X{
public:
vector v;
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), ...);
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};
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...);
}
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.