6

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.

Community
  • 1
  • 1
Mikhail
  • 20,685
  • 7
  • 70
  • 146
  • Iterate across the row first, and then iterate across columns. – CinchBlue Dec 09 '15 at 19:02
  • I am absolutely horrible, but the way it's written right now is "ambiguous." The structure is nested, but it doesn't have nested template iteration. So maybe try have the type specified for the top level struct and then make another one with the second level strict still with a variadic list. Then instantiate a specific case with the top level instantiated case. – CinchBlue Dec 09 '15 at 19:04
  • Do you really need a 2d array? You just found the solution for a 1d array – Bob__ Dec 09 '15 at 19:06
  • To clarify, if you don't nest the template level, the compiler will iterate through the rows and the columns simultaneously. But the way you want is, **for each row, for each column, calculate a value**. So for each row, specify an array of column values. Provide a fixed type instantiation for the row and leave an "open" variadic list for the columns. – CinchBlue Dec 09 '15 at 19:10
  • @Bob__ Yes, as you can see, I am ok with hard-coded values, so I have _some_ solution to my task. 1D-array would be a worse solution than hard-coded 2D-array, so I am not interested in it. – Mikhail Dec 09 '15 at 19:10
  • @VermillionAzure I would be happy to see the code :) – Mikhail Dec 09 '15 at 19:10
  • @Mikhail For some implementations, "2D-arrays" are equivalent to packed 1D-arrays in series. And I'm on the bus on mobile :( – CinchBlue Dec 09 '15 at 19:11
  • @VermillionAzure If you are not on the bus already, would you please share some code with your idea? :) – Mikhail Dec 12 '15 at 11:47
  • @Mikhail Alright, let me try a dive... – CinchBlue Dec 12 '15 at 20:45
  • @Mikhail Yeah this is hard. – CinchBlue Dec 17 '15 at 05:08
  • @VermillionAzure Thank you for your time :) – Mikhail Dec 17 '15 at 07:44
  • I am not very expirienced in metaprogramming, but I decided to try to do it in _compile-time_. It seems like we have to use a preprocessor macroses (because built-in array cannot be initialized in other way except explicitly defining in { }) in order to make all possible combinations of x and y and then expand them simultaneously. But I didn't find out how we can do it. – MrPisarik Jan 18 '16 at 16:45
  • If it hadn't been a question of curiosity then I'd have kept this computed matrix in binary file rather than made up tricky code. Because it's can unnecessarily increase size of executable file. And I think this way more flexible, because you can just substitute files with different matrices. – MrPisarik Jan 18 '16 at 17:00
  • 1
    @MrPisarik Agree. But it is indeed a question of curiosity. – Mikhail Jan 18 '16 at 17:26

1 Answers1

1

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

Diego91b
  • 121
  • 4
  • Thanks for the answer, but it only works in C++14, not in C++11, while I already have a solution for C++14. – Mikhail Jun 02 '17 at 13:45