Подтвердить что ты не робот

Создание статического массива с вариативными шаблонами

Был ответ на stackoverflow (который, как мне кажется, больше не может найти), который продемонстрировал, как вариационный шаблон можно использовать в С++ 11 для создания статического массива во время компиляции:

template <class T, T... args> 
struct array_
{
    static const T data[sizeof...(args)];
};

template <class T, T... args> 
const T array_<T, args...>::data[sizeof...(args)] = { args... };

Рекурсивная мета-функция может быть предоставлена ​​для создания экземпляра array_ с любым числом параметров, которое затем будет скопировано во время компиляции во внутренний массив. Это полезный способ создания мета-функций для генерации постоянных массивов во время компиляции.

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

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

4b9b3361

Ответ 1

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

Просто взгляните на приведенный ниже пример, который, я считаю, достаточно ясен, чтобы объяснять себя:

#include <iostream>

template<typename T>
class my_class
{
    public:
        my_class(T)
        {
            //construct
        }

        void print_something()
        {
            std::cout << "something\n";
        }
};

template<class C, class T, T ... args>
struct array_
{
        static C data[sizeof...(args)];
};

template<class C, class T, T ... args>
C array_<C, T, args...>::data[sizeof...(args)] = {C(args)...};

int main()
{
    array_<my_class<int> , int, 1, 200, 0, 42>::data[2].print_something();
}

Примечание: скомпилировано только под GCC 4.6

Ответ 2

В С++ 11 (и особенно в С++ 14) лучший способ инициализировать объекты во время компиляции - это конструкторы constexpr, а не путем воспроизведения метагеймов с системой типов.

struct MyObject {
    int x_, y_;
    constexpr MyObject(int x, int y) : x_(x), y_(y) { }
};

const MyObject array[] = { MyObject(1,2), MyObject(3,4) };

Вы также можете применить свою идею "функции генератора", если вы действительно хотите:

#include <stdio.h>

#if __cplusplus < 201400
template<size_t... II> struct integer_sequence { typedef integer_sequence type; };
template<size_t N, size_t... II> struct make_index_sequence;
template<size_t... II> struct make_index_sequence<0, II...> : integer_sequence<II...> {};
template<size_t N, size_t... II> struct make_index_sequence : make_index_sequence<N-1, N-1, II...> {};
#define HACK(x) typename x::type
#else
#include <utility>  // the C++14 way of doing things
using std::integer_sequence;
using std::make_index_sequence;
#define HACK(x) x
#endif


struct MyObject {
    int x_, y_;
    constexpr MyObject(int x, int y) : x_(x), y_(y) { }
};

template<typename T, int N, T (*Func)(int), typename Indices>
struct GeneratedArrayHelper;

template<typename T, int N, T (*Func)(int), size_t... i>
struct GeneratedArrayHelper<T, N, Func, integer_sequence<i...>> {
    static const T data[N];  // element i is initialized with Func(i)
};

template<typename T, int N, T (*Func)(int), size_t... i>
const T GeneratedArrayHelper<T,N,Func, integer_sequence<i...>>::data[N] =
    { Func(i)... };

template<typename T, int N, T (*Func)(int)>
struct GeneratedArray :
    GeneratedArrayHelper<T, N, Func, HACK(make_index_sequence<N>)> {};

constexpr MyObject newObject(int i) { return MyObject(2*i, 2*i+1); }

int main() {
    for (const MyObject& m : GeneratedArray<MyObject, 5, newObject>::data) {
        printf("%d %d\n", m.x_, m.y_);
    }

    // Output:
    //   0 1
    //   2 3
    //   4 5
    //   6 7
    //   8 9
}

Я не знаю, почему Clang 3.5 и GCC 4.8 настаивают на том, что я помещаю макрос HACK() там, но они отказываются компилировать код без него. Вероятно, я сделал какую-то тупую ошибку, и кто-то может это указать. Кроме того, я не уверен, что все const и constexpr находятся в лучших местах.

Ответ 3

Аргументы шаблона непигового типа также могут быть указателями или ссылками, если они указывают или ссылаются на объект с внешней связью.

template<typename T, T& t>
struct ref {
    static T&
    get() { return t; }
};

int i = 0;
int& ri = ref<int, i>::get(); // ok

static int j = 0;
int& rj = ref<int, j>::get(); // not ok

const int jj = 0; // here, const implies internal linkage; whoops
const int& rjj = ref<const int, jj>::get(); // not ok

extern const int k = 0;
const int& rk = ref<const int, k>::get(); // ok

namespace {
int l = 0;
}
int& rl = ref<int, l>::get(); // ok, and l is specific to the TU

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

// Not possible:
// const char* lits[] = { "Hello, ", "World!" };
// lit accepts const char*&, not const char*
// typedef array_<T, lit<lits[0]>, lit<lits[1]>, int_<42> > array;

// instead, but painful:
const char* hello = "Hello";
const char* world = "World!";
typedef array_<T, lit<hello>, lit<world>, int_<42> > array;
/*
 * here array::data would be an array of T, size 3,
 * initialized from { hello, world, 42 }
 */

Я не вижу, как избежать динамической инициализации без С++ 0x constexpr, и даже тогда есть ограничения. Использование некоторого типа кортежей для создания композитных инициализаторов (например, инициализация из { { hello, world, 42 }, ... }), оставленных как упражнение. Но вот пример.