Forgive me if this has been answered already, as I couldn\'t find it...
Basically I have an object that needs to take a variadic argument list in it\'s constructor
You can't use a variadic argument list unless it's a template, you can, as stated, use a initializer_list like this:
class GenericNode {
public:
GenericNode(std::initializer_list<GenericNode*> inputs) : inputs_(inputs)
{
}
private:
std::vector<GenericNode*> inputs_;
};
template <class ... T>
GenericNode* foo(T ... t)
{
return new GenericNode({t...});
}
Another way to do it:
#include <iostream>
#include <vector>
using std::vector;
template <typename T>
void variadic_vector_emplace(vector<T>&) {}
template <typename T, typename First, typename... Args>
void variadic_vector_emplace(vector<T>& v, First&& first, Args&&... args)
{
v.emplace_back(std::forward<First>(first));
variadic_vector_emplace(v, std::forward<Args>(args)...);
}
struct my_struct
{
template <typename... Args>
my_struct(Args&&... args)
{
variadic_vector_emplace(_data, std::forward<Args>(args)...);
}
vector<int>& data() { return _data; }
private:
vector<int> _data;
};
int main()
{
my_struct my(5, 6, 7, 8);
for(int i : my.data())
std::cout << i << std::endl;
}
// inputs_.push_back(inputs)...;
This doesn't work because you can't expand a parameter pack as a statement, only in certain contexts such as a function argument list or initializer-list.
Also your constructor signature is wrong, if you're trying to write a variadic template it needs to be a template!
Once you write your constructor signature correctly the answer is easy, just construct the vector with the pack expansion:
#include <vector>
class GenericNode
{
public:
template<typename... T>
GenericNode(T*... inputs) : inputs_{ inputs... }
{ }
private:
std::vector<GenericNode*> inputs_;
};
(You could instead have set it in the constructor body with:
inputs_ = { inputs... };
but the cool kids use member initializers not assignment in the constructor body.)
The downside of this solution is that the template constructor accepts any type of pointer arguments, but will then give an error when trying to construct the vector if the arguments aren't convertible to GenericNode*
. You could constrain the template to only accept GenericNode
pointers, but that's what happens automatically if you do what the other answers suggest and make the constructor take a std::initializer_list<GenericNode*>
, and then you don't need any ugly enable_if
SFINAE tricks.
I recently wrote the following function that takes a string with {1} , {2} , {3} ... in it and substitutes the argument list. I ran in to the same problem until I decided to let the compiler work it out for itself with the auto keyword.
#include <string>
#include <vector>
using std::string;
using std::vector;
template<typename S, typename... Args>
string interpolate( const S& orig , const Args&... args)
{
string out(orig);
auto va = {args...};
vector<string> v{va};
size_t i = 1;
for( string s: v)
{
string is = std::to_string(i);
string t = "{" + is + "}";
try
{
auto pos = out.find(t);
if(pos != out.npos)
{
out.erase(pos, t.length());
out.insert( pos, s);
}
i++;
}
catch( std::exception& e)
{
std::cerr << e.what() << std::endl;
}
} // for
return out;
}
Apparently that is good enough as long as the types line up correctly. In this case I am using only std::string throughout. I think this is an elegant technique, but it may have drawbacks.
The best thing would be to use an initializer list
#include <initializer_list>
#include <vector>
class GenericNode {
public:
GenericNode(std::initializer_list<GenericNode*> inputs)
:inputs_(inputs) {} //well that's easy
private:
std::vector<GenericNode*> inputs_;
};
int main() {
GenericNode* ptr;
GenericNode node{ptr, ptr, ptr, ptr};
} //compilation at http://stacked-crooked.com/view?id=88ebac6a4490915fc4bc608765ba2b6c
The closest to what you already have, using C++11 is to use the vector's initializer_list:
template<class ...Ts>
GenericNode(Ts... inputs)
:inputs_{inputs...} {} //well that's easy too
//compilation at http://stacked-crooked.com/view?id=2f7514b33401c51d33677bbff358f8ae
And here's a C++11 version with no initializer_lists at all. It's ugly, and complicated, and requires features missing from many compilers. Use the initializer list
template<class T>
using Alias = T;
class GenericNode {
public:
template<class ...Ts>
GenericNode(Ts... inputs) { //SFINAE might be appropriate
using ptr = GenericNode*;
Alias<char[]>{( //first part of magic unpacker
inputs_.push_back(ptr(inputs))
,'0')...,'0'}; //second part of magic unpacker
}
private:
std::vector<GenericNode*> inputs_;
};
int main() {
GenericNode* ptr;
GenericNode node(ptr, ptr, ptr, ptr);
} //compilation at http://stacked-crooked.com/view?id=57c533692166fb222adf5f837891e1f9
//thanks to R. Martinho Fernandes for helping me get it to compile
Unrelated to everything, I don't know if those are owning pointers or not. If they are, use std::unique_ptr
instead.
class Blob
{
std::vector<std::string> _v;
public:
template<typename... Args>
Blob(Args&&... args)
: _v(std::forward<Args>(args)...)
{ }
};
int main(void)
{
const char * shapes[3] = { "Circle", "Triangle", "Square" };
Blob b1(5, "C++ Truths");
Blob b2(shapes, shapes+3);
}
Example from C++11 Truths looks simple enough...;) Not a complete solution but might give you some ideas.