Действительно ли возможно создать и инициализировать массив шаблонного метапрограммирования использования значений?

Я хочу быть в состоянии создать массив расчетных значений (скажем, для пользы простоты, из которой я хочу, чтобы каждое значение было квадратом, он - индекс), во время компиляции использующий шаблонное метапрограммирование. Действительно ли это возможно? То, как делает каждое местоположение в массиве, инициализируется?

(Да, существуют более легкие способы сделать это, не обращаясь для шаблонной обработки метапрограммирования, просто задавшись вопросом, возможно ли сделать это с массивом.)

23
задан aneccodeal 9 February 2010 в 02:04
поделиться

5 ответов

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

template <int I>
struct squared {
    squared<I - 1> rest;
    int x;
    squared() : x((I - 1) * (I - 1)) {}
};

template <>
struct squared<1> {
    int x;
    squared() : x(0) {}
};

Позже в коде вы можете объявить:

squared<5> s;

, и компилятор действительно создаст struct , содержащую 5 int s: 0, 1, 4, 16, 25.

Пара замечаний:

  1. Моя интерпретация стандарта C ++ заключается в том, что он не дает гарантии , что эта структура будет построена идентично структуре множество.Хотя это тип POD, и типы POD гарантированно будут расположены «непрерывно» в памяти (1,8 / 5) с первым элементом по смещению 0 (9,2 / 17) и последующими элементами по более высоким адресам (9,2 / 12), и массивы также располагаются «смежно» (8.3.4 / 1), в стандарте не говорится, что массивы совместимы по макету с такими struct s. Однако любой здравомыслящий компилятор разместит эти объекты одинаково. [EDIT: как указывает ildjarn, наличие определяемого пользователем конструктора фактически делает этот класс неагрегированным и, следовательно, не-POD. Опять же, любой здравомыслящий компилятор не позволит этому повлиять на его макет.]
  2. C ++ требует, чтобы даже пустая структура была длиной не менее 1 байта. Если это не так, мы могли бы пойти с немного более чистой формулировкой, в которой базовый случай рекурсии был I == 0 , и мы не вычитали 1 из I для вычислений. .

Было бы хорошо, если бы мы могли поместить эту структуру внутри union с массивом подходящего размера, чтобы упростить доступ к членам. К сожалению, C ++ запрещает вам включать объект в объединение , если этот объект имеет нетривиальный конструктор. Итак, самый простой способ получить элемент i th - использовать старомодное доброе приведение:

squared<5> s;
cout << "3 squared is " << reinterpret_cast<int*>(&s)[3] << endl;

Если хотите, вы можете написать перегруженную функцию operator [] () шаблон, чтобы сделать его красивее.

14
ответ дан 29 November 2019 в 01:25
поделиться

Вы можете использовать это для массивов фиксированного размера:

int a[] = { foo<0>::value, foo<1>::value, ... };

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

Еще одна вещь, которую вы, возможно, захотите изучить, - это последовательности интегральных констант во время компиляции, например using Boost.MPL :

template<int n>
struct squares {
    typedef typename squares<n-1>::type seq;
    typedef typename boost::mpl::integral_c<int, n*n>::type val;    
    typedef typename boost::mpl::push_back<seq, val>::type type;
};

template<>
struct squares<1> {
    typedef boost::mpl::vector_c<int, 1>::type type;
};

// ...
typedef squares<3>::type sqr;
std::cout << boost::mpl::at_c<sqr, 0>::type::value << std::endl; // 1
std::cout << boost::mpl::at_c<sqr, 1>::type::value << std::endl; // 4
std::cout << boost::mpl::at_c<sqr, 2>::type::value << std::endl; // 9

(Обратите внимание, что это, вероятно, можно было бы сделать более элегантно, используя алгоритмы MPL)

Если вам больше интересно узнать о теме времени компиляции, книги "Modern C ++ Design » (основы TMP) и « Метапрограммирование шаблонов C ++ » (в основном, MPL подробно объясняется) заслуживают внимания.

4
ответ дан 29 November 2019 в 01:25
поделиться

Возможно, более разумно использовать специализации, чем фактический массив. Это становится запутанным ... Я сделал это для алгоритма хеширования строк, который я использовал с метапрограммированием шаблонов. В части алгоритма использовалась большая таблица поиска, которая в итоге выглядела так:

template <> struct CrcTab<0x00> { enum { value = 0x00000000 }; };
template <> struct CrcTab<0x01> { enum { value = 0x77073096 }; };
template <> struct CrcTab<0x02> { enum { value = 0xee0e612c }; };

. Доступ к ней был таким:

CrcTab<index>::value

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

Для значения квадрата аргумента вам даже не нужно специализироваться. Предупреждение: компилятор не удобен ...

template <uint32_t N> struct Square { enum { value = N*N }; };

На самом деле это подчеркивает недостаток этого подхода, вы не можете индексировать с помощью переменной ... только константы.

2
ответ дан 29 November 2019 в 01:25
поделиться

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

template<int N>
struct SqArr {
    static inline void fill(int arr[]) {
        arr[N-1] = (N-1)*(N-1);
        SqArr<N-1>::fill(arr);
    }
};

template<>
struct SqArr<0> {
    static inline void fill(int arr[]) { }
};

Теперь давайте попробуем использовать это чудовище:

#include <iostream>
#include <algorithm>
#include <iterator>

using namespace std;

int main(int argc, char* argv[])
{
    int arr[10];
    SqArr<10>::fill(arr);
    cout << endl;
    copy(arr, arr+10, ostream_iterator<int>(cout, "\t"));
    cout << endl;
    return 0;
}

Примечание (исповедь): Это не вычисления во время компиляции. Чтобы мотивировать это, можно попробовать изменить четвертую строку с SqArr::fill(arr); на SqArr::fill(arr);, чтобы рекурсия была бесконечной, и вы увидите, что это прекрасно компилируется (когда компилятор на самом деле должен рекурсировать бесконечно, или жаловаться на бесконечную рекурсию).

2
ответ дан 29 November 2019 в 01:25
поделиться

В метапрограммировании это называется Static Table Generation.

#include <iostream>

const int ARRAY_SIZE = 5;

template <int N, int I=N-1>
class Table : public Table<N, I-1>
{
public:
    static const int dummy;
};

template <int N>
class Table<N, 0>
{
public:
    static const int dummy;
    static int array[N];
};

template <int N, int I>
const int Table<N, I>::dummy = Table<N, 0>::array[I] = I*I + 0*Table<N, I-1>::dummy;

template <int N>
int Table<N, 0>::array[N];

template class Table<ARRAY_SIZE>;

int main(int, char**)
{
    const int *compilerFilledArray = Table<ARRAY_SIZE>::array;
    for (int i=0; i < ARRAY_SIZE; ++i)
        std::cout<<compilerFilledArray[i]<<std::endl;
}

Мы используем явное инстанцирование шаблона и фиктивную переменную, чтобы заставить компилятор заполнить массив квадратами индексов. Часть после I*I - это трюк, необходимый для рекурсивного присвоения каждого элемента массива.

15
ответ дан 29 November 2019 в 01:25
поделиться
Другие вопросы по тегам:

Похожие вопросы: