在 C++ 中以编程方式在编译时创建静态数组
Posted
技术标签:
【中文标题】在 C++ 中以编程方式在编译时创建静态数组【英文标题】:Programmatically create static arrays at compile time in C++ 【发布时间】:2011-02-28 00:38:49 【问题描述】:可以在编译时定义一个静态数组,如下:
const std::size_t size = 5;
unsigned int list[size] = 1, 2, 3, 4, 5 ;
问题 1 - 是否可以通过使用各种元编程技术在编译时“以编程方式”分配这些值?
问题 2 - 假设数组中的所有值都是相同的,除了少数几个,是否可以在编译时以编程方式选择性地分配值?
例如:
const std::size_t size = 7;
unsigned int list[size] = 0, 0, 2, 3, 0, 0, 0 ;
-
欢迎使用 C++0x 的解决方案
数组可能很大,很少
一百个元素长
现在的数组只包含
POD 类型
也可以假设大小为
数组将是预先知道的,
在静态编译时兼容
方式。
解决方案必须使用 C++ (无脚本、无宏、无 pp
或基于代码生成器的解决方案)
更新: Georg Fritzsche 的解决方案非常棒,需要一些工作才能使其在 msvc 和 intel 编译器上编译,但仍然是一种非常有趣的解决问题的方法。
【问题讨论】:
@GMan:图片就像我已经解释过的那样,想知道是否可以在编译时仅使用 c++ 填充静态数组。没有隐藏的议程等。 @Hippicoder @GMan 的评论是相关的,因为你不能在 C++ 或 C++0x 中做到这一点。为读者提供上下文,专家会为您找到原始问题的(替代)合适解决方案。 假设一个进程需要一个LUT,取决于进程的模式,除了一些值之外,LUT是相同的,所有其他值都是相同的,或者可以通过评估一个简单的序列来生成比如 f(n) = 2*n 或 f(n) = 1 + n 等等... 我认为第一个可以使用递归模板并将常量 + 1 传递到每个更深层次。我现在正在调查。 @Michael Dorgan:我也想过这个问题,但似乎无法找到正确的方法,我的解决方案涉及从模板结构的枚举中获取值,但是仍然需要我实例化 n 个模板,这大大增加了编译时间。 【参考方案1】:最接近的方法是使用 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";
【讨论】:
关于模板实例化深度的说明,msvc 在 1000 左右死亡,gcc 有一个设置递归深度的选项,我已经能够根据这个建议创建一个 512 元素的 lut - 编译时间显然是比在源代码中硬编码 lut 长一点,但总的来说它工作正常!!! :D 太棒了!它本质上允许连接/扩展我在 C++03 中无法使用元模板实现的数组。我认为您应该使用 MetaFunction 参数化ArrayHolder
,以便能够定义多个具有给定数量的数组。
作为一些编译器允许的相当有限的递归深度的一种解决方法,可以在每一步向“可变值列表”添加一个以上的值,将所需的深度降低 M 倍,其中 M 是添加的值的数量。例如,对于 M=2,我们有: templatestd::initializer_list
扔到窗外,直到我找到你的答案。在我理解这是如何工作的之前肯定需要一段时间,但我对这个从编译时到运行时的惊人桥梁感到敬畏。 TYVM。
@Xocoatzin 那是参数包扩展,参见例如here【参考方案2】:
嗯,你的要求太模糊了,很难对它们做任何事情......主要问题当然是:这些价值从何而来?
无论如何,在 C++ 中构建可以被认为是 4 个步骤:
预构建步骤:从其他格式生成头文件/源代码的脚本 预处理 模板实例化 正确编译如果您希望排除脚本生成,那么您有两种选择:预处理和元模板编程。
据我所知,元模板编程无法做到这一点,因为据我所知,在编译时连接两个数组是不可能的。因此,我们只剩下了今天的救星:预处理器编程
我建议使用成熟的库来帮助我们:Boost.Preprocessor。
这里特别感兴趣:
BOOST_PP_FOR BOOST_PP_REPEAT现在,如果我们知道从哪里选择值,我们就可以给出更有意义的例子。
【讨论】:
查看 Georg Fritzsche 的回答:使用 C++0x 可变参数模板和从可变参数列表初始化静态数组,他能够想出一个元模板解决方案!【参考方案3】:从 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;
();
如您所见,您甚至可以引用数组的前一个单元格。
这种技术称为 IILE 或立即调用 Lambda 表达式。
【讨论】:
【参考方案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;
当然,您可能会争辩说该数组不是在编译时初始化的(我认为这是不可能的),但是将进入数组的值是在编译时计算的,您可以像平常一样访问它们数组...我认为这是尽可能接近的。
【讨论】:
reinterpret_cast
在我脑海中敲响了未定义行为的警钟。
我们可以通过使用&f.i-10
或添加递归int* start()
函数来避免reinterpret_cast
。然而问题实际上是“编译器是否在嵌套结构中的contained
和i
之间插入填充?”。我看不出它为什么会这样,因为NestedStruct<N>
和int
将具有相同的对齐要求。但是,我认为规范中没有任何内容会禁止在这种情况下插入填充。 (也许比我肯定知道的更好的语言律师)。【参考方案5】:
你真的需要在编译时这样做吗?在静态初始化时这样做会容易得多。你可以这样做。
#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';
如果需要,您可以将列表推送到结构之外,但我认为这样更简洁。
【讨论】:
这些值在编译时不存在 - 我想如果我想要的很简单,我可以很容易地编写一个函数来填充 std::vector ......谢谢不过尝试一下。 @Hippicoder:如果这些值在编译时不存在,那么您将如何按照您的问题在编译时以编程方式分配它们? 我相信他是想说你的代码没有在编译时生成它们。您的代码在运行时创建数组,因此不符合他过于严格的要求...【参考方案6】:Boost.Assignment 之类的东西可以用于标准容器。如果确实需要使用数组,可以和Boost.Array一起使用。
【讨论】:
【参考方案7】:有时(并非总是)这样的数组是从类型数组生成的。 例如,如果您已经有可变参数类列表(如模板)并且想要存储封装的 uint32_t 值,您可以使用:
uint32_t tab[sizeof(A)]= A::value...;
【讨论】:
【参考方案8】:第一个问题。你可以这样做。
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;
【讨论】:
Ok.... 我如何从列表中获取第 i 个值,并在运行时生成 "i" ? ps:请阅读 Michael Dorgan 解决方案的评论。【参考方案9】:只需使用代码生成器。使用表格甚至数学函数构建一个或多个可以生成所需代码的模板。然后将您生成的文件包含在您的应用中。
说真的,代码生成器会让您的生活更轻松。
【讨论】:
有两个人将此标记为垃圾邮件。它对我来说似乎不是垃圾邮件,除了您的代码生成器尚不可用,因此提及它无助于回答问题。 (一旦你的工具可用就编辑答案会有所不同。) – 我也是代码生成的忠实粉丝,这真的会让他的生活更轻松。 ;) @Roger:我已经编辑了我的答案并删除了对产品的所有引用。 现在绝对值得一票!在 SO 上自我推销是一件棘手的事情。 代码生成器可以是array_type user_impl(size_t index);
,使用std::cout
和逗号生成数组体。您可以使用#include
将生成的正文包含在代码中。只需像运行时初始化一样对其进行编码,然后使用主机构建的二进制文件来生成数组。对于大多数用户来说,主机、构建和目标都是相同的。【参考方案10】:
元编程可以做很多事情。 但首先我想问:你为什么要在你的情况下这样做?我可以理解您是否需要在不同的地方声明这样的数组,这样就需要多次重写相同的东西。这是你的情况吗?
通过说“以编程方式定义”,我建议如下:
#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) ;
您还可以处理大多数数组条目相同的情况,并带有一些变态的创造力:)
但你应该经常问自己:这真的值得吗?因为如您所见,您将代码变成了一个谜题。
【讨论】:
为什么要将一些应该在编译时计算的东西推回运行时?由于 C++ 语言的差距,他不得不把代码变成一个谜题。【参考方案11】:来自提升,
boost::mpl::range_c<int,1,5>
将在编译时生成从 1 到 5 的排序数字列表。对于第二个,您没有提到将更改值的标准。我很确定一旦创建了一个列表,你就不能取消定义然后重新定义一个新的 var。
【讨论】:
带有 range_c 和其他 mpl 样式的数组是,它们没有随机访问运算符,或者如果它们有,则需要编译时索引值。我希望能够像在运行时使用静态数组一样使用该数组,并在运行时生成索引值。【参考方案12】:使用模板递归
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;
【讨论】:
【参考方案13】:数组 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 t[大小]
如果你真的想使用 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());
【讨论】:
【参考方案14】:随着时间的推移,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 甚至在constexpr
函数中增加了对std::string
和std::vector
的支持。但是,您无法从 constexpr
函数中返回非空字符串和向量,并且到目前为止,只有 Microsoft 编译器实现了此功能。
【讨论】:
以上是关于在 C++ 中以编程方式在编译时创建静态数组的主要内容,如果未能解决你的问题,请参考以下文章