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?
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};
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.
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), ...);
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...);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With