Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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);
like image 670
user3058378 Avatar asked Mar 22 '23 11:03

user3058378


1 Answers

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);
}
like image 71
mpark Avatar answered Apr 26 '23 15:04

mpark