【问题标题】:Create static array with variadic templates使用可变参数模板创建静态数组
【发布时间】:2011-08-28 22:35:33
【问题描述】:

stackoverflow 上有一个答案(我似乎再也找不到了),它演示了如何在 C++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_,然后在编译时将其复制到内部数组中。这是一种在编译时创建用于生成常量数组的元函数的有用方法。

然而,一个问题是它依赖于类模板参数来获取实际值来填充数组。这导致了一个主要限制:只有整数常量可以用作值模板参数。因此,您不能使用这种技术来生成自定义类型的数组。

我试图想办法解决这个限制,但想不出任何办法。有什么方法可以使这种技术适用于非整数常量?

【问题讨论】:

  • 一个 std::string 可能在内部有一个动态内存分配。你会如何在编译时做到这一点? :-)
  • @Bo,是的,std::string 是一个不好的例子。但是自定义 POD 结构呢?
  • 我相信这是问题中提到的link
  • 看我的问题,有一个很好的答案:stackoverflow.com/a/20388055/293195
  • @BoPersson 不可能创建编译时字符串,但可以创建编译时 c-string 并在运行时将其转换为字符串(或者,如果使用 c++17,则转换为 string_view in编译时也是)。 Here 您可以找到该技术的示例用法以及一些相关的元功能。

标签: c++ templates c++11 variadic-templates


【解决方案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】:

    在 C++11(尤其是 C++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() 宏放在那里,但他们拒绝在没有它的情况下编译代码。可能我犯了一些愚蠢的错误,有人可以指出。另外,我不确定所有consts 和constexprs 都在最好的位置。

    【讨论】:

    • 玩元编程游戏有一个很好的理由:如果您想确保每个表达式只有一个实例。您可以使用哈希表或其他东西,但如果您使用模板 foo,则不需要。
    【解决方案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
    

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

    // 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 的constexpr,我看不出如何避免动态初始化,即使这样也有限制。使用某种元组来构建复合初始化器(例如,从{ { hello, world, 42 }, ... } 初始化)作为练习。但是here's an example

    【讨论】:

    • std::string 无法构造 constexpr,因为它使用动态分配。
    • @Ben 实际上,由于我使用的是文字,因此在从文字组合初始化时会出现问题(请不要在上下文中引用我)。如果元素类型T 不能被静态初始化,那么显然这不会发生,但这与问题和我的答案无关。
    • 对于后代,template 参数不再需要具有外部链接,因此例如未命名命名空间中的东西是可以的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多