简体   繁体   中英

fill static templated arrays with metaprogramming and variadic templates

I know that there are easier ways to do it, but I would like to initialize at compilation time the map from unrolled index of 2d array to its general format.

I would like to do this without needing to instansiate the array object.

Below I define the map from array[][]->array[] . Now I wonder how to do the opposite: [] -> [][] without hardcoding the chosen mapping scheme.

I guess that should be possible using metaprogramming and variadic templates. But I tried using it for the first time just a couple of days ago, so it takes a while to get used to ;)

header:

template <int dim>
class internal {
    static unsigned int table[dim][dim];
    static unsigned int x_comp[dim*dim];
    static unsigned int y_comp[dim*dim];
};

source:

//1d case:

template <>
unsigned int
internal<1>::table[1][1]  = {{0}};

template <>
unsigned int
internal<1>::x_component[1] = {0};

template <>
unsigned int
internal<1>::y_component[1] = {0};

//2d case:

template<>
unsigned int
internal<2>::table[2][2] =
            {{0, 1},
             {2, 3}
            };

// here goes some metaprogramming tricks to initialize
// internal<2>::y_component[2*2] = ...
// internal<2>::x_component[2*2] = ... 
// based on mapping above, i.e. table[2][2];
// that is:
// x_table = { 0, 0, 1, 1 }
// y_table = { 0, 1, 0, 1 }
// 
// so that :
//
//  index == table[i][j]
//  i     == x_comp[index]
//  j     == y_comp[index]

EDIT1:

or just tell me that it's not possible and I hard-code everything or use integer division to relate the two index representations.

EDIT2 : i would prefer to stick with definition of arbitrary arrays. Of course one can do without, as in answer below using integer division.

Those arrays can be really arbitrary, for example:

template<>
unsigned int
internal<2>::table[2][2] =
            {{3, 0},
             {2, 1}
            };

Using arrays:

Given a table with unique entries from 0 to dim^2-1, you can write constexpr lookup functions for the i and j of a given table entry:

constexpr unsigned get_x_comp(unsigned index, unsigned i=0, unsigned j=0) 
{ return table[i][j] == index ? i : get_x_comp(index, ((j+1)%dim ? i : i+1), (j+1)%dim); }

constexpr unsigned get_y_comp(unsigned index, unsigned i=0, unsigned j=0) 
{ return table[i][j] == index ? j : get_y_comp(index, ((j+1)%dim ? i : i+1), (j+1)%dim); }

These will recursively call themselves, iterating through the table and looking for index . Recursion will eventually end when the given index is found and i / j of that index is returned.

Combine that with the C++14 std::integer_sequence mentioned by Jonathan to initialize the arrays:

template<unsigned... I>
constexpr auto make_x_comp(std::integer_sequence<unsigned, I...>) -> std::array<unsigned, sizeof...(I)> { return {get_x_comp(I)...}; }

Using metafunctions instead of arrays:

In some cicumstances, one might not even need arrays. I assume you want to the table to contain consecutive indices from 0 to dim^2-1. If that's the case, table , x_comp and y_comp are only simple compiletime functions with the following attributes:

  • table(i,j) := i*dim + j
  • x_comp(index) := index / dim (integer division)
  • y_comp(index) := index % dim

Depending on if you have C++11 features available, the implementation will be different, but both times without arrays.

Note: the following implementations will assume that the numbers stored in table are consecutive from 0 to dim^2-1. If that is not the case, you'll have to roll your own appropiate function for table and use the above get_x_comp and get_y_comp implementatio

C++11:

template <unsigned dim> //use unsigned to avoid negative numbers!
struct internal {
  static constexpr unsigned table(unsigned i, unsigned j) { return i*dim+j; }
  static constexpr unsigned x_comp(unsigned index) { return index/dim; }
  static constexpr unsigned y_comp(unsigned index) { return index%dim; }
};

You can call these functions like normal functions anywhere, especially anywhere you need compiletime constants. Example: int a[internal<5>::table(2,4)];

C++03:

template <unsigned dim> //use unsigned to avoid negative numbers!
struct internal {
  template<unsigned i, unsigned j>
  struct table{ static const unsigned value = i*dim+j; };
  template<unsigned index>
  struct x_comp{ static const unsigned value = index/dim; };
  template<unsigned index>
  struct y_comp{ static const unsigned value = index%dim; };
};

Using these metafunctions is a bit more clumsy than in C++11, but works as usual with template metafunctions. Same example as above: int a[internal<5>::table<2,4>::value];

Note: This time you can put the (meta-)functions in the header, since they are not non-integral static member variables any more. Also you do not need to restrict the template to small dimensions, since everything will be calculated well for dimensions less than sqrt(numeric_limits<unsigned>::max()) .

Edit : I didn't understand the rule for populating x_comp and y_comp when I wrote this, now that I see that part of the question this answer is not really relevant, because I was incorrectly assuming table only contained consecutive integers. The answer is left here anyway because Arne's (much better) answer refers to it.


I would replace the arrays with std::array and use the C++14 integer_sequence utility:

template <int dim>
struct internal {
    static std::array<std::array<unsigned, dim>, dim> table;
    static std::array<unsigned, dim*dim> x_comp;
    static std::array<unsigned, dim*dim> y_comp;
};

template<unsigned Origin, unsigned... I>
constexpr std::array<unsigned, sizeof...(I)>
make_1d_array_impl(std::integer_sequence<unsigned, I...>)
{
    return { { I + Origin ... } };
}

template<int N>
constexpr std::array<unsigned, N*N>
make_1d_array()
{
    return make_1d_array_impl<0>(std::make_integer_sequence<unsigned, N*N>{});
}


template<unsigned... I>
constexpr std::array<std::array<unsigned, sizeof...(I)>, sizeof...(I)>
make_2d_array_impl(std::integer_sequence<unsigned, I...> seq)
{
    return { { make_1d_array_impl<I*sizeof...(I)>(seq)  ... } };
}

template<int N>
constexpr std::array<std::array<unsigned, N>, N>
make_2d_array()
{
    return make_2d_array_impl(std::make_integer_sequence<unsigned, N>{});
}

template<int dim>
std::array<std::array<unsigned, dim>, dim> internal<dim>::table = make_2d_array<dim>();

That fills the table array correctly. I'll have to think about it a bit more to populate x_comp and y_comp as you want, but it's doable.

You can find an C++11 implementation of integer_sequence at https://gitlab.com/redistd/integer_seq/blob/master/integer_seq.h

I'm sorry if I'm not answering the question directly (or at all), but I don't really understand what you're asking. I think what you're saying is that you want to initialize at compilation time a way to have an array of size N x M represented as a 1D array?

I've included code that allows you to allocate non-square dimensions. I've built this in "easy" C++ so if you're just getting into templates it's not so difficult to follow.

Is it possible to do something like this?

template <typename T, typename std::size_t N, typename std::size_t M = 1>
class Array {
    T* data;
public:
    Array<T, N, M>() : data(new T[N * M]) {
        T temp = 0;
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < M; j++) {
                data[i * M + j] = temp++;
            }
        }
    }
    /* methods and stuff
}

Where M is the column number, so you'd use this like:

int main(void) {

    Array<float, 10, 10> myArray;

    return 0;
}

Remember to call delete in the destructor.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM