简体   繁体   English

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

[英]Programmatically create static arrays at compile time in C++

One can define a static array at compile time as follows:可以在编译时定义一个静态数组,如下所示:

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

Question 1 - Is it possible by using various kinds of metaprogramming techniques to assign these values "programmatically" at compile time?问题 1 - 是否可以使用各种元编程技术在编译时“以编程方式”分配这些值?

Question 2 - Assuming all the values in the array are to be the same barr a few, is it possible to selectively assign values at compile time in a programmatic manner?问题 2 - 假设数组中的所有值都相同,是否可以在编译时以编程方式有选择地分配值?

eg:例如:

const std::size_t size = 7;        
unsigned int list[size] = { 0, 0, 2, 3, 0, 0, 0 };
  1. Solutions using C++0x are welcome欢迎使用 C++0x 的解决方案
  2. The array may be quite large, few hundred elements long数组可能很大,有几百个元素长
  3. The array for now will only consist of POD types现在的数组将只包含 POD 类型
  4. It can also be assumed the size of the array will be known beforehand, in a static compile-time compliant manner.还可以假设数组的大小以静态编译时兼容的方式预先知道。
  5. Solutions must be in C++ (no script, no macros, no pp or code generator based solutions pls)解决方案必须是 C++ (没有脚本,没有宏,没有基于 pp 或代码生成器的解决方案)

UPDATE: Georg Fritzsche's solution is amazing, needs a little work to get it compiling on msvc and intel compilers, but nonetheless a very interesting approach to the problem.更新: Georg Fritzsche 的解决方案很棒,需要做一些工作才能在 msvc 和 intel 编译器上进行编译,但仍然是解决该问题的非常有趣的方法。

The closest you can get is using C++0x features to initialize local or member arrays of templates from a variadic template argument list.您可以获得的最接近的是使用 C++0x 功能从可变参数模板参数列表初始化模板的本地或成员数组。
This is of course limited by the maximum template instantiation depth and wether that actually makes a notable difference in your case would have to be measured.这当然受到最大模板实例化深度的限制,并且必须测量实际上在您的情况下产生显着差异的情况。

Example:例子:

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

Usage for your 1..5 case: 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";
}

Well your requirements are so vague it's difficult to do anything about them... The main issue is of course: where do those value come from ?嗯,你的要求太模糊了,很难对它们做任何事情......当然主要的问题是:这些价值从何而来?

Anyway a build in C++ can be thought of as 4 steps:无论如何,在 C++ 中的构建可以被认为是 4 个步骤:

  • Pre-build steps: script generation of header/source from other formats预构建步骤:从其他格式生成标题/源的脚本
  • Preprocessing预处理
  • Template instantiations模板实例化
  • Compilation proper编译正确

If you wish to rule out the script generation, then you're left with 2 alternatives: Preprocessing and Meta-template programming.如果您希望排除脚本生成,那么您只有两种选择:预处理和元模板编程。

There is just no way I know of for meta-template programming to do the trick here, because as far as I know it's not possible to concatenate two arrays at compile time.我不知道元模板编程可以在这里完成这个技巧,因为据我所知,在编译时连接两个数组是不可能的。 Thus we are left with the savior of the day: Preprocessor Programming因此,我们只剩下今天的救星了:预处理器编程

I would suggest using a full-fledged library to help us out: Boost.Preprocessor .我建议使用一个成熟的库来帮助我们: Boost.Preprocessor

Of particular interest here:这里特别有趣:

Now if only we knew where to pick the values from, we could give more meaningful examples.现在,如果我们知道从哪里选择值,我们就可以给出更有意义的例子。

Since C++17 you can use a constexpr lambda an invoke it in place.从 C++17 开始,您可以使用constexpr lambda 并就地调用它。 The only "downside" is that you will have to use std::array instead of c-style array:唯一的“缺点”是您必须使用std::array而不是 c 样式数组:

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

As an example that's how you could create an array with powers of two:作为一个例子,你可以如何创建一个具有 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;
}()};

As you can see, you can even reference the previous cells of the array.如您所见,您甚至可以引用数组的先前单元格。

This technique is called IILE or Immediately Invoked Lambda Expression.这种技术称为 IILE 或立即调用 Lambda 表达式。

How about building a nested struct using templates, and casting that as an array of the right type.如何使用模板构建嵌套结构,并将其转换为正确类型的数组。 The example below works for me, but I have a feeling I'm either treading in or walking very close to undefined behaviour.下面的示例对我有用,但我有一种感觉,我要么踏入其中,要么非常接近于未定义的行为。

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

And of course you could argue that the array is not initialised at compile time (which I think is impossible) but the values that will go into the array are calculated at compile time, and you can access them as you would a normal array... I think that's as close as you can get.当然,您可能会争辩说数组未在编译时初始化(我认为这是不可能的),但将在编译时计算将进入数组的值,您可以像访问普通数组一样访问它们。 . 我认为这是尽可能接近的。

Do you really need to do it at compiler time?你真的需要在编译时做吗? It would be much easier to do at static initialization time.在静态初始化时做会容易得多。 You could do something like this.你可以做这样的事情。

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

You could push the lists outside of the structs if you wanted but I thought it was a bit cleaner like this.如果您愿意,您可以将列表推送到结构之外,但我认为这样更简洁一些。

Something likeBoost.Assignment could work for standard containers.Boost.Assignment这样的东西可以用于标准容器。 If you really need to use arrays, you can use it along Boost.Array .如果你真的需要使用数组,你可以在Boost.Array 中使用它。

Sometime (not always) such array is generated from array of types.有时(并非总是)这样的数组是从类型数组生成的。 For example if you already have variadic class list (like template) and want to store encapsulated uint32_t value you can use:例如,如果您已经有可变参数类列表(如模板)并且想要存储封装的 uint32_t 值,您可以使用:

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

the 1't question.第一个问题。 You can do it like that.你可以这样做。

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

Just use a code generator.只需使用代码生成器。 Build one or more templates that can generate the code you want, using a table or even math functions.使用表格甚至数学函数构建一个或多个可以生成您想要的代码的模板。 Then include the file you generated in your app.然后包含您在应用程序中生成的文件。

Seriously, a code generator would make your life much easier.说真的,代码生成器会让你的生活更轻松。

There's a lot of things you can do with meta-programming.元编程可以做很多事情。 But first I'd like to ask: why would you want to do this in your case?但首先我想问:你为什么要在你的情况下这样做? I could understand if you needed to declare such an array in different places, so that it'd demand rewriting the same things multiple times.我可以理解您是否需要在不同的地方声明这样一个数组,以便它需要多次重写相同的内容。 Is this your case?这是你的情况吗?

By saying "define programmatically" I suggest the following:通过说“以编程方式定义”,我建议如下:

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

By now we've defined all the values you wanted in the most abstract way.到目前为止,我们已经以最抽象的方式定义了您想要的所有值。 BTW if those values actually mean something for you - you could add it to the declaration:顺便说一句,如果这些值实际上对您有意义 - 您可以将其添加到声明中:

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

Now let's breath life into the above declaration.现在让我们为上述声明注入活力。

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

You can also handle the situation where most of your array entries are the same, with some perverted creativity :)您还可以处理大多数数组条目相同的情况,但具有一些变态的创造力:)

But you should always ask yourself: is this really worth it?但你应该经常问自己:这真的值得吗? Because, as you can see, you turn the code into a puzzle.因为,正如你所看到的,你把代码变成了一个谜题。

from boost,从提升,

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

Will generate a list of sorted numbers from 1 to 5 at compile time.将在编译时生成从 1 到 5 的排序数字列表。 For the second, you mention no criteria for which values would be changed.对于第二个,您没有提到更改哪些值的标准。 I'm pretty sure you can't undef then redef a new var once a list is created.我很确定你不能取消定义然后在创建列表后重新定义一个新的 var。

use template recursive使用模板递归

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

array<int, SIZE> t数组<int, SIZE> t

As mentionned, with C++17 you can use constexpr如前所述,使用 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;
}

However you will have to use the c++ array type.但是,您必须使用 c++ 数组类型。


int t[SIZE]整数 t[大小]

If you really want to use a C array int [SIZE] , different from array<int, SIZE> use the following trick:如果你真的想使用一个 C 数组int [SIZE] ,与array<int, SIZE>不同array<int, SIZE>使用以下技巧:

Declare a global array and then compute values inside the main to create the static array at compile time:声明一个全局数组,然后在 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);
}


Results结果

Output at runtime (awful indeed):运行时输出(确实很糟糕):

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,...

Average Output with the constexpr array:使用 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,...

Average Output with the second method (slightly faster as we get rid of the overhead of C++ array):使用第二种方法的平均输出(随着我们摆脱 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,...

Benchmark基准

I benchmarked with:我的基准测试:

#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());
}

Over time, the capabilities of constexpr functions, methods and lambdas have greatly improved in C++.随着时间的推移, constexpr函数、方法和 lambda 的功能在 C++ 中得到了极大的改进。 With C++17, you may use for loops and if conditions to actually calculate the contents of an constexpr array at compile time.在 C++17 中,您可以使用 for 循环和 if 条件在编译时实际计算constexpr数组的内容。 See this example for a prime number sieve:请参阅此示例以了解质数筛:

#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>() };

When you look at the assembly output of this code, you will see only the data bytes of the myprimes array, but not a single processor instruction.当您查看此代码的汇编输出时,您只会看到myprimes数组的数据字节,而看不到单个处理器指令。 All calculations are performed at compile time, even if optimization is turned off.即使关闭优化,所有计算都在编译时执行。

However, as others have already written: Interpreting C++ code in the compiler is much slower than running compiled C++ code.但是,正如其他人已经写过的那样:在编译器中解释 C++ 代码比运行编译后的 C++ 代码慢得多。 So those initializations, that can reasonably be done at compile time, would take at most a few milliseconds at run time.因此,那些可以在编译时合理完成的初始化在运行时最多需要几毫秒。

But const / constexpr initialization has many advantages.但是const / constexpr初始化有很多优点。 Namely they go to constant memory, that is shared between different processes running the same application.即它们进入常量内存,即在运行相同应用程序的不同进程之间共享。 On the other hand, dynamic initialization at run time goes to private memory of each process.另一方面,运行时的动态初始化进入每个进程的私有内存。

And the capabilities are further improving.并且能力正在进一步提高。 C++20 even adds support for std::string and std::vector in constexpr functions. C++20 甚至在constexpr函数中添加了对std::stringstd::vector支持。 However, you are not able to return non-empty strings and vectors from constexpr functions, and until now, only the Microsoft compiler has implemented this feature.但是,您无法从constexpr函数返回非空字符串和向量,并且直到现在,只有 Microsoft 编译器实现了此功能。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM