Можно ли создать и инициализировать массив значений с помощью шаблонного метапрограммирования? - PullRequest
23 голосов
/ 09 февраля 2010

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

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

Ответы [ 7 ]

15 голосов
/ 17 августа 2010

Это называется Генерация статической таблицы в метапрограммировании.

#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 - это трюк, необходимый для рекурсивного присваивания каждому элементу массива.

14 голосов
/ 09 февраля 2010

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

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 ++ заключается в том, что он не соответствует , гарантирующему , что этот struct будет размещен идентично массиву. Несмотря на то, что это тип POD, и типы POD гарантированно будут размещаться «непрерывно» в памяти (1.8 / 5) с первым членом со смещением 0 (9.2 / 17) и более поздними элементами по более высоким адресам (9.2 / 12), и массивы также размещаются «смежно» (8.3.4 / 1), в стандарте не говорится, что массивы совместимы с макетом с такими struct с. Однако любой здравомыслящий компилятор выложит эти объекты одинаково. [EDIT: как указывает ildjarn, наличие определяемого пользователем конструктора фактически делает этот класс неагрегированным и, следовательно, не POD. Опять же, любой здравомыслящий компилятор не позволит этому влиять на его компоновку.]
  2. C ++ требует, чтобы даже пустой struct был длиной не менее 1 байта. Если это не так, мы могли бы пойти с немного более чистой формулировкой, в которой базовый случай рекурсии был I == 0, и мы не вычли 1 из I для расчетов.

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

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

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

13 голосов
/ 18 июля 2011

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

//typedefs used
typedef short int              index_t;
typedef unsigned long long int int_t;

//standard recursive template for coefficient values, used as generator
template <index_t n, index_t k> struct coeff {static int_t const value = coeff<n-1, k-1>::value + coeff<n-1, k>::value;};
template <index_t n>            struct coeff<n, 0> {static int_t const value = 1;};
template <index_t n>            struct coeff<n, n> {static int_t const value = 1;};

//helper template, just converts its variadic arguments to array initializer list
template<int_t... values> struct int_ary {static int_t const value[sizeof...(values)];};
template<int_t... values> int_t const int_ary<values...>::value[] = {values...};

//decrement k, pile up variadic argument list using generator
template<index_t n, index_t k, int_t... values> struct rec: rec<n, k-1, coeff<n, k-1>::value, values...> {};
//when done (k == 0), derive from int_ary
template<index_t n,            int_t... values> struct rec<n, 0, values...>: int_ary<values...> {};

//initialise recursion
template<index_t n> struct binomial: rec<n, n+1> {};

Для доступа к элементам используйте синтаксис, такой как биномиальный :: value [k], где N - постоянная времени компиляции, а k - индекс в диапазоне от 0 до N включительно.

4 голосов
/ 25 мая 2016

Мне очень нравится ответ j_random_hackers - это красиво и коротко. С C ++ 11 можно расширить это до

template <int I>
struct squared {
  squared<I - 1> rest;
  static const int x = I * I ;
  constexpr int operator[](int const &i) const { return (i == I ?  x : rest[i]); }
  constexpr int size() const { return I; }
};

template <>
struct squared<0> {
  static const int x = 0;
  constexpr int operator[](int const &i) const { return x; }
  constexpr int size() const { return 1; }
};

Этот код может использоваться как во время выполнения

  squared<10> s;

  for(int i =0; i < s.size() ; ++i) {
    std::cout << "s[" << i << "]" << " = " << s[i] << std::endl;
  }

а также время компиляции

struct Foo {
  static const squared<10> SquareIt;
  enum { 
    X = 3,
    Y = SquareIt[ X ]
  };
};

int main() {
  std::cout << "Foo::Y = " << Foo::Y << std::endl;
}

и обеспечивает приятный и читаемый синтаксис.

4 голосов
/ 09 февраля 2010

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

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

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

Еще одна вещь, на которую вы могли бы обратить внимание, это последовательности во время компиляции целочисленных констант, например, используя 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)

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

2 голосов
/ 09 февраля 2010

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

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<N-1>::fill(arr); на SqArr<N>::fill(arr);, чтобы рекурсия была бесконечной, и вы увидите, что это прекрасно скомпилируется (когда на самом деле компилятор должен повторяться бесконечно или жаловаться на бесконечную рекурсию).

2 голосов
/ 09 февраля 2010

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

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

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

...