Создание статического массива с вариативными шаблонами
Был ответ на 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_
с любым числом параметров, которое затем будет скопировано во время компиляции во внутренний массив. Это полезный способ создания мета-функций для генерации постоянных массивов во время компиляции.
Однако одна из проблем заключается в том, что она зависит от параметров шаблона класса, чтобы получить фактические значения для заполнения массива. Это приводит к одному главному ограничению: в качестве параметров шаблона значения могут использоваться только интегральные константы. Таким образом, вы не можете использовать этот метод для создания массивов пользовательских типов.
Я пытался подумать о чем-то, чтобы обойти это ограничение, но ничего не могу придумать. Есть ли способ заставить эту технику работать с нецелыми константами?
Ответы
Ответ 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 }, ... }
), оставленных как упражнение. Но вот пример.