C++14: How to group variadic inputs by template parameter?

  • A+
Category:Languages

Say I have two classes:

template <unsigned N> class Pixel {     float color[N]; public:     Pixel(const std::initializer_list<float> &il)     {       // Assume this code can create a Pixel object from exactly N floats, and would throw a compiler error otherwise      } };  template <unsigned N> class PixelContainer {     std::vector<Pixel<N>> container; }; 

What I'm trying to do is to write a constructor for PixelContainer such that: It would instantiate correctly for the following cases (example, not exhaustive):

PixelContainer<3> pc1(1, 2, 3)          // Creates a container containing one Pixel<3> objects PixelContainer<3> pc2(1, 2, 3, 4, 5, 6) // Creates a container containing 2 Pixel<3> objects PixelContainer<2> pc3(1, 2, 3, 4, 5, 6) // Creates a container containing 3 Pixel<2> objects 

It would not compile for the following cases (as example, not exhaustive):

PixelContainer<3> pc4(2, 3) // Not enough arguments PixelContainer<2> pc5(1, 2, 3, 4, 5) // Too many arguments 

How do I achieve the above using template meta-programming? I feel it should be achievable, but can't figure out how. Specifically, I do not want to be doing the grouping myself e.g

PixelContainer<2> pc2({1, 2}, {3, 4}, {5, 6}) // Creates a container containing 3 Pixel<2> objects 

(See this question for the inspiration behind mine)

 


template<class T, std::size_t I, std::size_t...Offs, class Tup> T create( std::index_sequence<Offs...>, Tup&& tup ) {   return T( std::get<I+Offs>(std::forward<Tup>(tup))... ); }  template <unsigned N> struct Pixel {     float color[N];      template<class...Ts,         std::enable_if_t< sizeof...(Ts)==N, bool > = true     >     Pixel(Ts&&...ts):color{ std::forward<Ts>(ts)... } {}; };  template <unsigned N> struct PixelContainer {      std::vector<Pixel<N>> container;     template<class T0, class...Ts,       std::enable_if_t<!std::is_same<std::decay_t<T0>, PixelContainer>{}, bool> =true     >     PixelContainer(T0&& t0, Ts&&...ts):       PixelContainer( std::make_index_sequence<(1+sizeof...(Ts))/N>{}, std::forward_as_tuple( std::forward<T0>(t0), std::forward<Ts>(ts)... ) )     {}     PixelContainer() = default; private:   template<class...Ts, std::size_t...Is>   PixelContainer( std::index_sequence<Is...>, std::tuple<Ts&&...>&& ts ):     container{ create<Pixel<N>, Is*N>( std::make_index_sequence<N>{}, std::move(ts) )... }   {} }; 

create takes a type, a starting index, and a index sequence of offsets. Then it takes a tuple.

It then creates the type from the (starting index)+(each of the offsets) and returns it.

We use this in the private ctor of PixelContainer. It has an index sequence element for each of the Pixels whose elements are in the tuple.

We multiply the index sequence element by N, the number of elements per index sequence, and pass that to create. Also, we pass in an index sequence of 0,...,N-1 for the offsets, and the master tuple.

We then unpack that into a {} enclosed ctor for container.

The public ctor just forwards to the private ctor with the right pack of indexes of one-per-element (equal to argument count/N). It has some SFINAE annoyance enable_if_t stuff to avoid it swallowing stuff that should go to a copy ctor.

Live example.

Also,

  std::enable_if_t<0 == ((sizeof...(Ts)+1)%N), bool> =true 

could be a useful SFINAE addition to PixelContainer's public ctor.

Without it, we simply round down and discard "extra" elements passed to PixelContainer. With it, we get a "no ctor found" if we have extra elements (ie, not a multiple of N).

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: