user3058378
user3058378

Reputation: 43

Can a multidimensional array be filled from variadic template?

So i have something like that:

template<unsigned int W,unsigned int H>
class Class
{
    int data[W][H];
    Class(const (&_data)[W][H])
    {
        for (int x=0;x<W;x++)
            for (int y=0;y<H;y++)
                data[x][y] = _data[x][y];
    }
    template<class... args>
    Class()
    {
        /// black magic
    }
}

What could i replace the "black magic", so the second constructor will accept W*H ints? Example:

Class<3,2> class1(1,2,3,4,5,6);

Upvotes: 4

Views: 802

Answers (4)

mpark
mpark

Reputation: 7904

There are alternative answers which might be more practical and simpler to implement, but I'll show how you could actually do this with a compile-time for-loop for purposes of demonstrating black-magic.

Here is a compile-time for-loop.

/* Compile-time for-loop up to N. */
template <std::size_t N>
struct For {

  /* Call f<I>(args...) N times. */
  template <typename F, typename... Args>
  void operator()(F &&f, Args &&... args) const {
    Impl<0, N>()(std::forward<F>(f), std::forward<Args>(args)...);
  }

  private:

  /* Forward declaration. */
  template <std::size_t I, std::size_t End>
  struct Impl;

  /* Base case. Do nothing. */
  template <std::size_t End>
  struct Impl<End, End> {

    template <typename F, typename... Args>
    void operator()(F &&, Args &&...) const { /* Do nothing. */ }

  };  // Impl<End, End>

  /* Recursive case. Call f<I>(args...), then recurse into next step. */
  template <std::size_t I, std::size_t End>
  struct Impl {

    template <typename F, typename... Args>
    void operator()(F &&f, Args &&... args) const {
      std::forward<F>(f).template operator()<I>(std::forward<Args>(args)...);
      Impl<I + 1, End>()(std::forward<F>(f), std::forward<Args>(args)...);
    }

  };  // Impl<I, End>

};  // For<N>

Here is a simple use case of it.

struct Print {

  template <std::size_t I>
  void operator()(int x, int y) const {
    std::cout << "Iteration " << I << ": " << x << ' ' << y << std::endl;
  }

};  // Print

For<3>()(Print(), 1, 2);

Outputs

Iteration 0: 1 2
Iteration 1: 1 2
Iteration 2: 1 2

Now with this we can nest this compile-time for-loop just like how we could nest a run-time for-loop. Here is the Matrix class using this For<> template.

/* Defines an M by N Matrix, (Row by Col). */
template <std::size_t M, std::size_t N>
class Matrix {
  public:

  /* Our underlying M by N matrix. */
  using Data = std::array<std::array<int, N>, M>;

  /* Construct off of M * N arguments. */
  template <typename... Args>
  Matrix(Args &&... args) {
    static_assert(sizeof...(Args) == M * N,
                  "The number of arguments provided must be M * N.");
    ForEach(AssignImpl(),
            data_,
            std::forward_as_tuple(std::forward<Args>(args)...));
  }

  /* Print each element out to std::cout. */
  void Write(std::ostream &strm) const {
    ForEach(WriteImpl(), strm, data_);
  }

  private:

  /* Our outer for loop. Call InnerFor() M times.
     Resembles: 'for (std::size_t i = 0 ; i < M; ++i) {' */
  template <typename F, typename... Args>
  void ForEach(F &&f, Args &&... args) const {
    For<M>()(InnerFor(), std::forward<F>(f), std::forward<Args>(args)...);
  }

  /* Our inner for loop. Call ForBody() N times.
     Resembles: 'for (std::size_t j = 0; j < N; ++j) {' */
  struct InnerFor {

    template <std::size_t I, typename F, typename... Args>
    void operator()(F &&f, Args &&... args) const {
      For<N>()(ForBody<I>(),
               std::forward<F>(f),
               std::forward<Args>(args)...);
    }

  };  // InnerFor

  /* The body of our for loop. Call f<I, J>(args...); */
  template <std::size_t I>
  struct ForBody {

    template <std::size_t J, typename F, typename... Args>
    void operator()(F &&f, Args &&... args) const {
      std::forward<F>(f)
          .template operator()<I, J>(std::forward<Args>(args)...);
    }

  };  // ForBody<I>

  /* Given the M by N array and a tuple of length M * N, assign the array. */
  struct AssignImpl {

    template <std::size_t I, std::size_t J, typename Arg>
    void operator()(Data &data, Arg &&arg) const {
      data[I][J] = std::get<I * N + J>(std::forward<Arg>(arg));
    }

  };  // AssignImpl

  /* Given an output stream and our data, print the data at (I, J). */
  struct WriteImpl {

    template <std::size_t I, std::size_t J>
    void operator()(std::ostream &strm, const Data &data) const {
      strm << data[I][J] << std::endl;
    }

  };  // WriteImpl

  /* Our underlying M by N array. */
  Data data_;

};  // Matrix

Here is a quick demonstration of construction and writing to std::cout.

int main() {
  Matrix<3, 2> matrix{101, 102,
                      201, 202,
                      301, 302};
  matrix.Write(std::cout);
}

Upvotes: 3

Koushik Shetty
Koushik Shetty

Reputation: 2176

you could use std::initializer_list as a constructor parameter, since your array only is of type int[][].

Class(std::initializer_list<int> il)
{
    if(il.size() < W*H)
        throw string("Insufficient elements");//if lesser no. of elements are given.
    auto it = il.begin();// iterator to the first element of the list.
    for(int i =0;i< W;++i)
    {
        for(int j =0; j < H;++j)
        {
            data[i][j] = *it++;// increment the iterator
        }
    }
}

call site will look like this:

Class<3,2> c{1,2,3,4,5,6};

or

Class<3,2> c = {1,2,3,4,5,6};

if more elements are given then extras are discarded. initializer_list can give type safety, and will give you a diagnostic if narrowing is found.

Upvotes: 0

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275330

This works:

#include <array>
#include <utility>
#include <iostream>

template<unsigned W,unsigned H>
struct Foo
{
  std::array<std::array<int, H>, W> data;
  template<typename... Args>
  Foo(Args&&... args):
    data{ std::forward<Args>(args)... }
  {}
};
int main()
{
  Foo<2,2> x(1,2,3,4);
  for( auto&& a : x.data ) {
    for( unsigned z : a ) {
      std::cout << z << ",";
    }
  std::cout << "\n";
}

but exposes the order of storage in your inner array.

std::array is a syntactic sugar wrapped around raw C arrays.

Upvotes: 2

Felix Glas
Felix Glas

Reputation: 15524

First of all, there's some syntax errors in your example as missing semicolon after class declaration and the constructors being private.

Apart from that, if you want to store the numbers in row-major order, then you should declare your matrix/2d-array as int data[H][W] (height first, then width).

To store the values from a variadic pack you can simply expand them inside the ctor of a container, e.g. std::array, and make use of list-initialization.

template <typename... Args>
Class(Args&&... args) {
    const std::array<int, H * W> temp{std::forward<Args>(args)...};
    /* ... */
};

I've also used universal references and perfect forwarding to preserve the reference type of the pack.

To populate your 2d-array you simply have to iterate over all elements in temp and store them in the member array.

for (std::size_t h = 0; h != H; ++h)
    for (std::size_t w = 0; w != W; ++w)
        data[h][w] = temp[h * W + w];

See a full example here

Upvotes: 2

Related Questions