以编译方式在C ++中编译时创建静态数组

时间:2010-06-04 22:44:38

标签: c++ metaprogramming static-array

可以在编译时定义静态数组,如下所示:

const std::size_t size = 5;    
unsigned int list[size] = { 1, 2, 3, 4, 5 };

问题1 - 是否可以通过使用各种元编程技术在编译时“以编程方式”分配这些值?

问题2 - 假设数组中的所有值都是相同的barr,是否可以在编译时以编程方式有选择地分配值?

例如:

const std::size_t size = 7;        
unsigned int list[size] = { 0, 0, 2, 3, 0, 0, 0 };
  1. 欢迎使用C ++ 0x的解决方案
  2. 阵列可能很大,很少 百元长
  3. 现在的数组只包含 POD类型
  4. 也可以假设它的大小 阵列将事先知道, 在静态编译时兼容 方式。
  5. 解决方案必须使用C ++ (没有脚本,没有宏,没有pp 或基于代码生成器的解决方案请
  6. 更新: Georg Fritzsche的解决方案非常棒,需要一些工作才能在msvc和intel编译器上进行编译,但这仍然是解决问题的一种非常有趣的方法。

14 个答案:

答案 0 :(得分:76)

最接近的是使用C ++ 0x功能从可变参数模板参数列表初始化模板的本地或成员数组。
这当然受到最大模板实例化深度的限制,并且实际上会对您的情况产生显着差异进行测量。

示例:

template<unsigned... args> struct ArrayHolder {
    static const unsigned data[sizeof...(args)];
};

template<unsigned... args> 
const unsigned ArrayHolder<args...>::data[sizeof...(args)] = { args... };

template<size_t N, template<size_t> class F, unsigned... args> 
struct generate_array_impl {
    typedef typename generate_array_impl<N-1, F, F<N>::value, args...>::result result;
};

template<template<size_t> class F, unsigned... args> 
struct generate_array_impl<0, F, args...> {
    typedef ArrayHolder<F<0>::value, args...> result;
};

template<size_t N, template<size_t> class F> 
struct generate_array {
    typedef typename generate_array_impl<N-1, F>::result result;
};

1..5案例的用法:

template<size_t index> struct MetaFunc { 
    enum { value = index + 1 }; 
};

void test() {
    const size_t count = 5;
    typedef generate_array<count, MetaFunc>::result A;

    for (size_t i=0; i<count; ++i) 
        std::cout << A::data[i] << "\n";
}

答案 1 :(得分:6)

你的要求是如此模糊,很难对它们采取任何行动......主要问题当然是:这些价值来自哪里?

无论如何,C ++中的构建可以被认为是4个步骤:

  • 预构建步骤:从其他格式生成标题/来源的脚本
  • 预处理
  • 模板实例化
  • 正确编译

如果您希望排除脚本生成,那么您将有两种选择:预处理和元模板编程。

我不知道元模板编程在这里做的诀窍,因为据我所知,在编译时不可能连接两个数组。因此,我们留下了当时的救星:预处理程序编程

我建议使用完整的图书馆来帮助我们:Boost.Preprocessor

特别感兴趣的是:

现在,只要我们知道从哪里挑选价值,我们就可以提供更有意义的例子。

答案 2 :(得分:4)

如何使用模板构建嵌套结构,并将其转换为正确类型的数组。下面的例子适用于我,但我有一种感觉,我要么正在踩踏,要么走得非常接近未定义的行为。

#include <iostream>

template<int N>
struct NestedStruct
{
  NestedStruct<N-1> contained;
  int i;
  NestedStruct<N>() : i(N) {}
};

template<>
struct NestedStruct<0> 
{
  int i;
  NestedStruct<0>() : i(0) {}
};

int main()
{
  NestedStruct<10> f;
  int *array = reinterpret_cast<int*>(&f);
  for(unsigned int i=0;i<10;++i)
  {
    std::cout<<array[i]<<std::endl;
  }
}

当然你可以争辩说数组在编译时没有初始化(我认为这是不可能的)但是进入数组的值是在编译时计算的,你可以像正常一样访问它们数组......我认为这就像你能得到的一样接近。

答案 3 :(得分:2)

你真的需要在编译时这样做吗?在静态初始化时更容易做到。你可以这样做。

#include <cstddef>
#include <algorithm>

template<std::size_t n>
struct Sequence
{
    int list[n];

    Sequence()
    {
        for (std::size_t m = 0; m != n; ++m)
        {
            list[m] = m + 1;
        }
    }
};

const Sequence<5> seq1;

struct MostlyZero
{
    int list[5];

    MostlyZero()
    {
        std::fill_n(list, 5, 0); // Not actually necessary if our only
                                 // are static as static objects are
                                 // always zero-initialized before any
                                 // other initialization
        list[2] = 2;
        list[3] = 3;
    }
};

const MostlyZero mz1;

#include <iostream>
#include <ostream>

int main()
{
    for (std::size_t n = 0; n != 5; ++n)
    {
        std::cout << seq1.list[n] << ", " << mz1.list[n] << '\n';
    }
}

如果你愿意的话,你可以将结构列表推到结构之外,但我认为它有点像这样清洁。

答案 4 :(得分:2)

Boost.Assignment这样的东西可以用于标准容器。如果您确实需要使用数组,可以在Boost.Array使用它。

答案 5 :(得分:2)

有时(并非总是)此类数组是从类型数组生成的。 例如,如果您已经拥有可变参数类列表(如模板)并且想要存储封装的uint32_t值,则可以使用:

uint32_t tab[sizeof(A)]= {A::value...};

答案 6 :(得分:1)

问题不是问题。你可以这样做。

template <int num, int cur>
struct ConsequentListInternal {
    enum {value = cur};
    ConsequentListInternal<num-1,cur+1> next_elem;
};

template <int cur>
struct ConsequentListInternal<0, cur> {
    enum {value = cur};
};

template <int v>
struct ConsequentList {
    ConsequentListInternal<v, 0> list;
};

int main() {
    ConsequentList<15> list;
    return 0;
}

答案 7 :(得分:0)

元编程可以做很多事情。 但首先我想问一下:你为什么要在你的情况下这样做呢?我可以理解你是否需要在不同的地方声明这样的数组,以便它需要多次重写相同的东西。这是你的情况吗?

通过说“以编程方式定义”,我建议如下:

#define MyArr(macro, sep) \
    macro(0) sep \
    macro(0) sep \
    macro(2) sep \
    macro(3) sep \
    macro(0) sep \
    macro(0) sep \
    macro(0)

到目前为止,我们已经以最抽象的方式定义了您想要的所有值。顺便说一句,如果这些值实际意味着什么 - 你可以将它添加到声明中:

#define MyArr(macro, sep) \
    macro(0, Something1) sep \
    macro(0, Something2) sep \
    // ...

现在让我们为上述宣言注入生命。

#define NOP
#define COMMA ,
#define Macro_Count(num, descr) 1
#define Macro_Value(num, descr) num

const std::size_t size = MyArr(Macro_Count, +); 
unsigned int list[size] = { MyArr(Macro_Value, COMMA) };

您还可以处理大多数数组条目相同的情况,并且有一些变态的创造力:)

但是你应该总是问自己:这真的值得吗?因为,正如您所看到的,您将代码变成了一个谜题。

答案 8 :(得分:0)

来自boost,

boost::mpl::range_c<int,1,5>

将在编译时生成1到5的已排序数字列表。对于第二个,您没有提到要更改哪些值的标准。我很确定你不能取消,然后在创建一个列表后重新设置一个新的var。

答案 9 :(得分:0)

只需使用代码生成器。使用表格甚至数学函数构建一个或多个可以生成所需代码的模板。然后包含您在应用中生成的文件。

说真的,代码生成器会让你的生活更轻松。

答案 10 :(得分:0)

使用模板递归

template<uint64_t N>
constexpr uint64_t Value()
{
    return N + 100;
}

// recursive case
template<uint64_t N, uint64_t... args>
struct Array : Array<N - 1, Value<N - 1>(), args...> {
};

// base case
template<uint64_t... args>
struct Array<0, Value<0>(), args...> {
    static std::array<uint64_t, sizeof...(args) + 1> data;
};

template<uint64_t... args>
std::array<uint64_t, sizeof...(args) + 1> Array<0, Value<0>(), args...>::data = {Value<0>(), args...};

int main()
{
    Array<10> myArray;
    for (size_t i = 0; i < myArray.data.size(); ++i) {
        cout << myArray.data[i] << endl;
    }

    return 0;
}

答案 11 :(得分:0)

从C ++ 17开始,您可以使用constexpr lambda并在适当位置调用它。唯一的“缺点”是您将不得不使用std::array而不是c样式的数组:

constexpr auto myArray{[]() constexpr{
    std::array<MyType, MySize> result{};
    for (int i = 0; i < MySize; ++i)
    {
       result[i] = ...
    }
    return result;
}()};

作为示例,您将可以创建具有2的幂的数组:

constexpr auto myArray{[]() constexpr{
    constexpr size_t size = 64;
    std::array<long long, size> result{};
    result[0] = 1;
    for (int i = 1; i < size; ++i)
    {
       result[i] = result[i - 1] * 2;
    }
    return result;
}()};

如您所见,您甚至可以引用数组的先前单元格。

答案 12 :(得分:0)

array t

如前所述,在 C++17 中你可以使用 constexpr

vector<int> countBits(int num) {
    static constexpr int SIZE = 100000;
    static constexpr array<int, SIZE> t {[]() constexpr {
            constexpr uint32_t size = SIZE;
            array<int, size> v{};
            for (int i = 0; i < size; i++)
                v[i] =  v[i>>1] + (i & 1); // or simply v[i] = __builtin_popcount(i);
            return v;}()};

    vector<int> v(t.begin(), t.begin() + num + 1);
    return v;
}

但是您必须使用 c++ 数组类型。


int [尺寸]

如果你真的想使用一个 C 数组 int [SIZE],不同于 array<int, SIZE>,请使用以下技巧:

声明一个全局数组,然后在 main 中计算值以在编译时创建静态数组:

int w[100000] = {0};

vector<int> countBits(int num) {
    vector<int> v(w, w + num + 1);
    return v;
}

int main(void) {
    for (int i = 0; i < 100000; i++)
        w[i] = __builtin_popcount(i);
}


结果

运行时输出(确实很糟糕):

OK  ( 591 cycles)        0,1,1, -> 0,1,1,
OK  ( 453 cycles)        0,1,1,2,1,2, -> 0,1,1,2,1,2,
OK  ( 455 cycles)        0,1,1,2,1,2,2,3,1,2,... -> 0,1,1,2,1,2,2,3,1,2,...

使用 constexpr 数组的平均输出:

OK  (   1 cycles)        0,1,1, -> 0,1,1,
OK  (   2 cycles)        0,1,1,2,1,2, -> 0,1,1,2,1,2,
OK  (  24 cycles)        0,1,1,2,1,2,2,3,1,2,... -> 0,1,1,2,1,2,2,3,1,2,...

使用第二种方法的平均输出(速度稍快,因为我们摆脱了 C++ 数组的开销):

OK  (   0 cycles)        0,1,1, -> 0,1,1,
OK  (   1 cycles)        0,1,1,2,1,2, -> 0,1,1,2,1,2,
OK  (  23 cycles)        0,1,1,2,1,2,2,3,1,2,... -> 0,1,1,2,1,2,2,3,1,2,...

基准

我的基准测试:

#include <vector>
#include <string>
#include <cstdint>
#include <array>
#include <iostream>
#include <ctime>
#include <iterator>
#include <sstream>

using namespace std;

vector<int> nums = {2, 5};
vector<vector<int>> expected = {{0,1,1}, {0,1,1,2,1,2}}; // feel free to add more tests

for (int i = 0; i < expected.size(); i++) {
        clock_t start = clock();
        vector<int> res = countBits(nums[i]);
        double elapsedTime = (clock() - start);
        printf("%s  \033[30m(%4.0lf cycles)\033[0m\t %s -> %s\n", (expected[i] == res) ? "\033[34mOK" : "\033[31mKO", elapsedTime, toString(res).c_str(), toString(expected[i]).c_str());
}

答案 13 :(得分:0)

随着时间的推移,constexpr 函数、方法和 lambda 的功能在 C++ 中得到了极大的改进。在 C++17 中,您可以使用 for 循环和 if 条件在编译时实际计算 constexpr 数组的内容。请参阅此示例以了解质数筛:

#include <array>
#include <cmath>

template<unsigned N>
constexpr auto primesieve() {
    std::array<bool, N+1> primes {};
    // From C++20, the init loop may be written as:   primes.fill(true);
    for(unsigned n = 0; n <= N; n++) {
        primes[n] = true;
    }
    unsigned maxs = sqrt(N);
    for(unsigned n = 2; n <= maxs; n++) {
        if(primes[n]) {
            for(unsigned j = n + n; j <= N; j += n) {
                primes[j] = false;
            }
        }
    }
    return primes;
};

extern constexpr std::array<bool, 20> myprimes { primesieve<19>() };

当您查看此代码的汇编输出时,您只会看到 myprimes 数组的数据字节,而看不到单个处理器指令。即使关闭优化,所有计算都在编译时执行。

然而,正如其他人已经写的那样:在编译器中解释 C++ 代码比运行编译后的 C++ 代码慢得多。因此,那些可以在编译时合理完成的初始化在运行时最多需要几毫秒。

但是 const / constexpr 初始化有很多优点。即它们进入常量内存,即在运行相同应用程序的不同进程之间共享。另一方面,运行时动态初始化会进入每个进程的私有内存。

而且能力正在进一步提高。 C++20 甚至在 std::string 函数中添加了对 std::vectorconstexpr 的支持。但是,您无法从 constexpr 函数返回非空字符串和向量,并且直到现在,只有 Microsoft 编译器实现了此功能。