可以在编译时定义静态数组,如下所示:
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 };
更新: Georg Fritzsche的解决方案非常棒,需要一些工作才能在msvc和intel编译器上进行编译,但这仍然是解决问题的一种非常有趣的方法。
答案 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)
如前所述,在 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++ 数组类型。
如果你真的想使用一个 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::vector
和 constexpr
的支持。但是,您无法从 constexpr
函数返回非空字符串和向量,并且直到现在,只有 Microsoft 编译器实现了此功能。