68

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

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 };
  1. 欢迎使用 C++0x 的解决方案
  2. 数组可能很大,几百个元素长
  3. 现在的数组将只包含 POD 类型
  4. 还可以假设以静态编译时兼容的方式预先知道数组的大小。
  5. 解决方案必须使用 C++ (无脚本、无宏、无 pp 或基于代码生成器的解决方案)

更新: Georg Fritzsche 的解决方案非常棒,需要一些工作才能在 msvc 和 intel 编译器上进行编译,但仍然是一种非常有趣的解决问题的方法。

4

14 回答 14

87

您可以获得的最接近的方法是使用 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";
}
于 2010-06-05T18:54:12.703 回答
8

从 C++17 开始,您可以使用constexprlambda 并就地调用它。唯一的“缺点”是您必须使用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 表达式。

于 2020-07-26T09:58:19.320 回答
6

好吧,您的要求是如此模糊,很难对它们做任何事情......主要问题当然是:这些价值来自哪里?

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

  • 预构建步骤:从其他格式生成头文件/源代码的脚本
  • 预处理
  • 模板实例化
  • 正确编译

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

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

我建议使用一个成熟的库来帮助我们:Boost.Preprocessor

这里特别感兴趣:

现在,如果我们知道从哪里选择值,我们就可以给出更有意义的例子。

于 2010-06-05T10:12:28.503 回答
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;
  }
}

当然,您可能会争辩说该数组未在编译时初始化(我认为这是不可能的),但将进入数组的值是在编译时计算的,您可以像访问普通数组一样访问它们。 .我认为这是尽可能接近的。

于 2010-06-05T04:11:57.330 回答
2

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

uint32_t tab[sizeof(A)]= {A::value...};
于 2011-08-20T21:03:16.747 回答
2

Boost.Assignment这样的东西可以适用于标准容器。如果你真的需要使用数组,你可以和Boost.Array一起使用。

于 2010-06-04T23:21:57.063 回答
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';
    }
}

如果你愿意,你可以将列表推到结构之外,但我认为这样更干净一些。

于 2010-06-04T23:11:10.467 回答
1

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

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

于 2010-06-05T19:08:08.153 回答
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;
}
于 2010-06-04T23:40:21.453 回答
0

从提升,

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

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

于 2010-06-04T23:29:21.387 回答
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) };

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

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

于 2010-06-04T23:15:00.960 回答
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;
}
于 2020-07-08T03:55:02.537 回答
0

数组<int, SIZE> 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++ 数组类型。


整数 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());
}
于 2021-04-19T14:43:25.717 回答
0

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

#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和函数的支持。但是,您无法从函数中返回非空字符串和向量,并且到目前为止,只有 Microsoft 编译器实现了此功能。std::vectorconstexprconstexpr

于 2021-04-30T08:57:37.373 回答