заполнять статические массивы шаблонов метапрограммированием и вариативными шаблонами

Я знаю, что есть более простые способы сделать это, но я хотел бы инициализировать во время компиляции карту из развернутого индекса 2-го массива в его общий формат.

Я хотел бы сделать это без необходимости создавать экземпляр объекта массива.

Ниже я определяю карту от array[][]->array[]. Теперь мне интересно, как сделать наоборот: [] -> [][] без жесткого кодирования выбранной схемы сопоставления.

Я предполагаю, что это должно быть возможно с использованием метапрограммирования и вариативных шаблонов. Но я впервые попробовал его использовать буквально пару дней назад, так что нужно время, чтобы привыкнуть;)

заголовок:

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

источник:

//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]

РЕДАКТИРОВАТЬ1:

или просто скажите мне, что это невозможно, и я все жестко запрограммирую или использую целочисленное деление, чтобы связать два представления индекса.

EDIT2: я бы предпочел придерживаться определения произвольных массивов. Конечно, можно обойтись и без, как в ответе ниже, используя целочисленное деление.

Эти массивы могут быть действительно произвольными, например:

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

person Denis    schedule 18.06.2013    source источник
comment
Какие ценности вы хотели бы видеть в internal<2>::x_component и т. Д.?   -  person Arne Mertz    schedule 18.06.2013
comment
@ArneMertz Я изменил вопрос в конце, чтобы прояснить   -  person Denis    schedule 18.06.2013
comment
можно ли использовать функции C ++ 11 как std::array и constexpr? Если вы хотите иметь только эти индексы, вам не понадобится весь этот массив.   -  person Arne Mertz    schedule 18.06.2013
comment
@ArneMertz std :: array и constexpr в порядке. Я использую constexpr в других местах   -  person Denis    schedule 18.06.2013


Ответы (3)


Использование массивов:

Учитывая таблицу с уникальными записями от 0 до dim ^ 2-1, вы можете написать constexpr функции поиска для i и j данной записи таблицы:

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

Они будут рекурсивно вызывать себя, перебирая таблицу и ища index. Рекурсия в конечном итоге закончится, когда данный индекс будет найден и будет возвращено _4 _ / _ 5_ этого индекса.

Объедините это с C ++ 14 std::integer_sequence, упомянутым Джонатаном, для инициализации массивов:

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

Использование метафункций вместо массивов:

В некоторых случаях массивы могут даже не понадобиться. Я предполагаю, что вы хотите, чтобы table содержал последовательные индексы от 0 до dim ^ 2-1. В этом случае table, x_comp и y_comp - это только простые функции времени компиляции со следующими атрибутами:

  • table(i,j) := i*dim + j
  • x_comp(index) := index / dim (целочисленное деление)
  • y_comp(index) := index % dim

В зависимости от того, доступны ли у вас функции C ++ 11, реализация будет отличаться, но в обоих случаях без массивов.

Примечание. в следующих реализациях предполагается, что числа, хранящиеся в table, идут последовательно от 0 до dim ^ 2-1. Если это не случай, вам придется свернуть свою собственную подходящую функцию для table и использовать указанные выше реализации get_x_comp и get_y_comp.

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; }
};

Вы можете вызывать эти функции как обычные функции где угодно, особенно когда вам нужны константы времени компиляции. Пример: 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; };
};

Использование этих метафункций немного более неуклюже, чем в C ++ 11, но работает как обычно с метафункциями шаблонов. Тот же пример, что и выше: int a[internal<5>::table<2,4>::value];

Примечание. На этот раз вы можете поместить (мета) функции в заголовок, поскольку они больше не являются неотъемлемыми статическими переменными-членами. Также вам не нужно ограничивать шаблон небольшими размерами, так как все будет хорошо рассчитано для размеров меньше sqrt(numeric_limits<unsigned>::max()).

person Arne Mertz    schedule 18.06.2013
comment
Спасибо Арне за ответ. Извините, что не сразу сказал, что мне нужны эти массивы. В противном случае это правда, без массивов можно легко обойтись целочисленным делением. - person Denis; 18.06.2013
comment
@Denis, вы можете использовать подход Джонатана для Populationg table, я добавил методику заполнения x_comp функциями constexpr. Смотрите мои правки. - person Arne Mertz; 18.06.2013
comment
Отлично. Первая часть этого ответа великолепна, но остальная часть предполагает последовательные числа в table, а редактирование ответа дает понять, что это может быть не так (что делает мой ответ неправильным). Чтобы справиться с общим случаем, решение должно использовать get_x_comp и get_y_comp для поиска значений в table, что мне не удалось. - person Jonathan Wakely; 18.06.2013

Извините, если я не отвечаю на вопрос напрямую (или вообще), но я действительно не понимаю, о чем вы спрашиваете. Я думаю, вы говорите, что хотите инициализировать во время компиляции способ представления массива размером N x M в виде одномерного массива?

Я включил код, позволяющий выделять неквадратные размеры. Я построил это на "простом" C ++, поэтому, если вы только начинаете знакомиться с шаблонами, это не так уж и сложно.

Можно ли сделать что-то подобное?

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
}

Где M - номер столбца, поэтому вы должны использовать это так:

int main(void) {

    Array<float, 10, 10> myArray;

    return 0;
}

Не забудьте вызвать delete в деструкторе.

person Tyler Jandreau    schedule 18.06.2013
comment
это требует создания экземпляра объекта этого класса. Это именно то, чего я не хочу делать. Все равно спасибо за ответ - person Denis; 18.06.2013

Изменить: я не понимал правила заполнения x_comp и y_comp, когда писал это, теперь, когда я вижу, что часть вопроса, этот ответ на самом деле не актуален, потому что я ошибочно предполагал, что table содержит только последовательные целые числа. Ответ в любом случае остается здесь, потому что ответ Арне (гораздо лучший) относится к нему.


Я бы заменил массивы на std::array и использовал бы утилиту C ++ 14 integer_sequence:

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>();

Это правильно заполняет массив table. Мне придется подумать об этом еще немного, чтобы заполнить x_comp и y_comp, как вы хотите, но это выполнимо.

Вы можете найти реализацию integer_sequence в C ++ 11 на https://gitlab.com/redistd/integer_seq/blob/master/integer_seq.h

person Jonathan Wakely    schedule 18.06.2013
comment
Я не совсем понимаю, как вы использовали определение internal<2>::table[2][2]? - person Denis; 18.06.2013
comment
Я не использовал internal<2>, я показал, как определить и заполнить internal<N>::table для любого N. Я не понимаю, какие значения вы хотите в массивах x и y, хотя - person Jonathan Wakely; 18.06.2013
comment
значения находятся в разделе комментариев. По сути, это карта между двумя представлениями 2d-массива - парой индексов (x, y) и одним (развернутым) индексом. Этот internal<2>::table[2][2] определяет пару- ›индекс; Вопрос в том, как заполнить пару индекс - ›; - person Denis; 18.06.2013