Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initialize plain 2D array with a given function on compile time

I want to create a 2D-array populated by some known function with no runtime overhead.

To have an example, suppose a function f(x, y) = 10 * y + x, let x be in {1, 2, 3} and y in {4, 5, 6}. I want to create a 2D array with content

41 42 43
51 52 53
61 62 63

Now, the easiest way to go is just hard-code the values directly in my sources. And it is indeed appropriate for my task, so the question is just out of curiosity.

I would like to create a metafunc and a struct possessed with some kind of black magic, which allows me to define an array out of given sets of values for x and y. Like this:

template<int X> struct Func {
  template<int Y> struct over {
    static const int value = 10 * Y + X;  // f(x, y)
  };
};

template<int... args1> struct Rows {
  template<int... args2> struct Cols {
    static const int data[sizeof...(args1)][sizeof...(args2)];
  };
};

template<int... args1>
template<int... args2>
const int Rows<args1...>::Cols<args2...>::data[sizeof...(args1)][sizeof...(args2)] = {
  { Func<args1>::over<args2>::value... }  // This does not do what I want :(
                                          // Need some black magic here
};

// Here is my precious table
const auto& table = Rows<1, 2, 3>::Cols<4, 5, 6>::data;

If I print values from the table, I have this:

41 52 63
 0  0  0
 0  0  0

I understand what's happening, the term Func<args1>::over<args2>::value has two parameter packs in it, args1 and args2, so applying ... on it expand them simultaneously, and I only have 3 members instead of 9.

If you've reached so far, you've already understood what I want. So the question is, how do I do it? How do I apply ellipsis separately to both parameter packs so I can have cartesian combination in the initializer? Or maybe there are some other ways to do it?

I am aware of this answer and that answer. They use std::array instead of plain array, so they first construct 1D-arrays, and then initialize 2D-array with a number of 1D-array. But if I understood correctly, this initialization has to be done in runtime. I want to avoid that. However, I have no objections against std::array. I suppose that with a proper compiler they are just as fast as plain arrays.

By the way, here is my possible solution using generalized constexpr from C++14 and a question about it. Any ideas on how to solve the task with constexpr from C++11 are also welcomed.

like image 353
Mikhail Avatar asked Dec 09 '15 18:12

Mikhail


1 Answers

The only way I found it is to separate the parameter packs by commas and expand one of them, and then to expand the other from the outside:

#include <array>
#include <utility>

using namespace std;

template<class T, T Y, T... Xs>
constexpr array<T, sizeof...(Xs)> a1{10*Y+Xs...};

template<class T, T... Xs, T... Ys>
constexpr auto a2(integer_sequence<T, Xs...>, integer_sequence<T, Ys...>) {
    return array<array<T, sizeof...(Xs)>, sizeof...(Ys)>{a1<T, Ys, Xs...>...};
}

array<array<int, 3>, 3> table(a2(
    integer_sequence<int, 1, 2, 3>(),
    integer_sequence<int, 4, 5, 6>()
));

The asm result is this:

table:
        .long   41
        .long   42
        .long   43
        .long   51
        .long   52
        .long   53
        .long   61
        .long   62
        .long   63

Code in Compiler Explorer

like image 107
Diego91b Avatar answered Oct 01 '22 21:10

Diego91b