Reputation: 3612
I have some templated classes like the ones below
template<typename T>
class A{
public:
A(T a0, T a1, T a2):a0_(a0),a1_(a1),a2_(a2){}
private:
T a0_,a1_,a2_;
};
template<typename T>
class B{
public:
B(T a, std::vector<T> b):a_(a),b_(b){}
private:
T a_;
std::vector<T> b_;
};
template<typename T>
class C{
public:
C(T a, T b, std::vector<T> c):a_(a),b_(b),c_(c){}
private:
T a_,b_;
std::vector<T> c_;
};
In general the classes constructor have an arbitrary number of arguments and optionally a std::vector
in the end.
I wish to make a getter function that will allocate an instance of the above classes.
template< typename C, typename... Args>
C* get(Args... args){
return new C(args...);
}
The getter function compiles successfully when the vector is explicitly created/defined.
A<int>* aa = get< A<int> >(1,2,3);
std::vector<int> v({1,2,3});
B<int>* bb = get< B<int> >(1,v);
C<int>* cc = get< C<int> >(1,2,v);
B<int>* bb = get< B<int> >(1,std::vector<int>({1,2,3}));
C<int>* cc = get< C<int> >(1,std::vector<int>({1,2,3}));
For usage simplicity, I would like to use initializer list to define the vector in B
and C
. This works fine when calling constructor directly.
A<int>* aa = new A<int>(1,2,3);
B<int>* bb = new B<int>(1,{1,2,3});
C<int>* cc = new C<int>(1,2,{1,2,3});
The getter function however gives a compile error
B<int>* bb = get< B<int> >(1,{1,2,3});
C<int>* cc = get< C<int> >(1,2,{1,2,3});
error: too many arguments to function ‘C* get(Args ...) [with C = B; Args = {}]’
Creating a function specialization handling the vector like below was unsuccessful, too.
template< typename C, typename T, typename... Args>
C* get(Args... args, std::vector<T> v){
return new C(args...,v);
}
Is it possible to create a getter function that will get the argument pack and the initializer list as last argument and create the object?
I use gcc 5.4 to compile.
Upvotes: 2
Views: 361
Reputation: 66200
My suggestion is: initializer_list
first.
If you accept that the initializer_list
is the first argument of get()
, you can write it as
template <template<typename> class C, typename T, typename... Args>
C<T>* get(std::initializer_list<T> il, Args ... args){
return new C<T>(args..., il);
}
calling it as
B<int>* bb = get<B, int>({1,2,3}, 1);
C<int>* cc = get<C, int>({1,2,3}, 1, 2);
If you what the initializer_list
in last position, there are problems in deduction of Args...
pack of types.
Obviously you need another version of get()
for not vector
C<T>
classes.
OT suggestion: you're using C++11, so you can (and I strongly suggest it) use smart pointers.
By example, using unique_ptr
, your get()
function could become
template <template<typename> class C, typename T, typename... Args>
std::unique_ptr<C<T>> get(std::initializer_list<T> il, Args ... args)
{ return std::unique_ptr<C<T>>(new C<T>(args..., il)); }
used as follows
std::unique_ptr<B<int>> bb { get<B, int >({1,2,3}, 1) };
std::unique_ptr<C<int>> cc { get<C, int >({1,2,3}, 1, 2) };
--- EDIT ---
The OP ask
In any case, long story short, the answer to my question is: No, I cannot have the initializer_list last. Right?
Never say "I cannot" but...
If you really, really want the initializer_list
in last position... and if you can accept that the Args...
argument are packed in a std::tuple
...
template <std::size_t ...>
struct range
{ };
template <std::size_t N, std::size_t ... Next>
struct rangeH
{ using type = typename rangeH<N-1U, N-1U, Next ... >::type; };
template <std::size_t ... Next >
struct rangeH<0U, Next ... >
{ using type = range<Next ... >; };
template <template<typename> class C, typename T, typename ... Args,
std::size_t ... I>
std::unique_ptr<C<T>> getH(std::tuple<Args...> const & t,
std::initializer_list<T> const & il,
range<I...> const)
{ return std::unique_ptr<C<T>>(new C<T>(std::get<I>(t)..., il)); }
template <template<typename> class C, typename T, typename... Args>
std::unique_ptr<C<T>> get(std::tuple<Args...> const & t,
std::initializer_list<T> const & il)
{ return getH<C, T>(t, il, typename rangeH<sizeof...(Args)>::type()); }
used as follows
std::unique_ptr<B<int>> bb { get<B, int >(std::make_tuple(1), {1,2,3}) };
std::unique_ptr<C<int>> cc { get<C, int >(std::make_tuple(1, 2), {1,2,3}) };
But... you really need the initializer_list
is last position?
I think this last solution is horrible (compared with the preceding)
Upvotes: 2
Reputation: 13698
It can't deduce initializer_list
out of what you give it so the most simple way is to tell what it is explicitly. And to make it more concise you could use something like this:
template<typename T>
using IL = std::initializer_list<T>;
C<int>* cc2 = get< C<int> >(1, 2, IL<int>{1,2,3});
Upvotes: 1