ztik ztik - 1 month ago 17
C++ Question

Template function getting argument pack and initializer list

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.

Answer

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)