26

There was an answer on stackoverflow (which I can't seem to find anymore) which demonstrated how a variadic template can be used in C++11 to create a static array at compile time:

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

A recursive meta-function could be provided to instantiate array_ with any number of parameters, which will then be copied at compile time into the internal array. It's a useful way to create meta-functions for generating constant arrays at compile time.

However, one problem is that it depends on class template parameters to get the actual values to populate the array. This results in one major limitation: only integral constants can be used as value template parameters. So, you can't use this technique to generate arrays of custom types.

I tried to think of something to work around this limitation, but can't come up with anything. Is there any way to make this technique work with non-integral constants?

4

3 回答 3

6

好吧,您确实可以使用自定义类型(即类)实例填充静态数组,前提是它们可以从整数类型(或任何其他类型可以作为非模板参数提供,我不会在此列举)构造。

看看下面的例子,我相信它足够清楚,可以自我解释:

#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 下编译得很好

于 2011-06-20T15:13:10.000 回答
2

In C++11 (and especially in C++14), the best way to initialize objects at compile-time is with constexpr constructors, not by playing metagames with the typesystem.

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

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

You can apply your "generator function" idea here, too, if you really want to:

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

I don't know why Clang 3.5 and GCC 4.8 insist that I put the HACK() macro in there, but they refuse to compile the code without it. Probably I made some dumb mistake and someone can point it out. Also, I'm not confident that all the consts and constexprs are in the best places.

于 2014-02-10T23:27:27.153 回答
1

非类型模板参数也可以是指针或引用,只要它们指向或引用具有外部链接的对象。

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

我不认为你真的想用外部引用来初始化元素,因为这最终会导致对象数量的两倍。您可以从文字初始化数组的元素,但不幸的是,您不能使用字符串文字作为模板参数所以你需要众所周知的间接层:这很痛苦,因为数组或数组引用不能出现在模板参数列表中(我想这就是字符串文字不能出现的原因):

// 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 }
 */

如果没有 C++0x's ,我看不出如何避免动态初始化constexpr,即使这样也有限制。使用某种元组来构建复合初始化器(例如,从 初始化{ { hello, world, 42 }, ... })作为练习。但这里有一个例子

于 2011-05-19T16:11:37.257 回答