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}
};
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)...}; }
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
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)];
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.