ztik
ztik

Reputation: 3612

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.

Upvotes: 2

Views: 361

Answers (2)

max66
max66

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

ixSci
ixSci

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

Related Questions