【问题标题】:Initialize an std::array algorithmically at compile time在编译时以算法方式初始化 std::array
【发布时间】:2019-10-16 10:00:23
【问题描述】:

考虑:

static constexpr unsigned num_points{ 7810 };
std::array< double, num_points > axis;

for (int i = 0; i < num_points; ++i)
{
    axis[i] = 180 + 0.1 * i;
}

axis 是一个类范围的常量。我想避免像任何其他全局变量一样初始化它。可以在编译时完成吗?


这是最后一个类:

// https://www.nist.gov/pml/atomic-spectroscopy-compendium-basic-ideas-notation-data-and-formulas/atomic-spectroscopy
// https://www.nist.gov/pml/atomic-spectra-database
struct Spectrum
{
    static constexpr unsigned _num_points{ 7810 };
    using Axis = std::array< double, _num_points >;

    static constexpr Axis _x{ [] ()            // wavelength, nm
        {
            Axis a {};
            for( unsigned i = 0; i < _num_points; ++i )
            {
                a[ i ] = 180 + 0.1 * i;
            }
            return a;
        } () };
    Axis _y {};                                // radiance, W·sr−1·m−2
};

代码和变量的混合是难看的,但至少公式就在读者的眼前。任何其他解决方案都需要大量输入才能获得类内定义的常量和类型。

或者如果我改变我的壁炉,我可以简单地在运行时返回 lambda。

【问题讨论】:

  • 如果您的数据在这种模式下确实是只读的,那么对于大多数硬件上的大多数用例,您最好在运行时计算它。 7810 * 8 字节对于阵列来说是一个很大的缓存占用空间。加载一个基数 + 比例因子只有 2 个双精度数 = 16 个字节。运行时计算很便宜:一次 int->FP 转换,一次 FMA 或一次 mul + add。 (加上加载常量)。所以是的,在缓存命中时,LUT 更快,但尤其是在循环内重复使用时,计算通常会很好。

标签: c++ initialization c++17 compile-time stdarray


【解决方案1】:

为了完整起见,这里的版本不需要定义函数,而是使用 lambda。 C++17 引入了在常量表达式中使用 lambda 的能力,因此您可以声明数组 constexpr 并使用 lambda 对其进行初始化:

static constexpr auto axis = [] {
    std::array<double, num_points> a{};
    for (int i = 0; i < num_points; ++i) {
        a[i] = 180 + 0.1 * i;
    }
    return a;
}();

(请注意最后一行中的(),它会立即调用 lambda。)

如果您不喜欢 axis 声明中的 auto,因为它使读取实际类型变得更加困难,但您不想在 lambda 中重复该类型,您可以改为:

static constexpr std::array<double, num_points> axis = [] {
    auto a = decltype(axis){};
    for (int i = 0; i < num_points; ++i) {
        a[i] = 180 + 0.1 * i;
    }
    return a;
}();

【讨论】:

  • 你不需要 lambda 中的 constexpr :)
  • @Rakete1111 你是对的。 lambda 的调用运算符会自动标记为 constexpr。
【解决方案2】:

这是完整的可编译代码:

#include <array>

template<int num_points>
static constexpr std::array<double, num_points> init_axis() {
    std::array<double, num_points> a{};
    for(int i = 0; i < num_points; ++i) 
    {
        a[i] = 180 + 0.1 * i;
    }
    return a;
};

struct Z {
    static constexpr int num_points = 10;
    static constexpr auto axis = init_axis<num_points>();
};

【讨论】:

  • 建议用std::array&lt;double,num_points&gt; 代替auto,这样读者就不必检查初始化函数来知道类型了。
  • @doug 我发现这是偏好问题。有些人喜欢知道类型,有些人不喜欢typeing(双关语自然!)。
  • 出于同样的原因,我也更喜欢汽车。但是当阅读这行代码的人(包括一个月后的我)看不到类型时,我会额外输入。可能对读者提出问题也有帮助。
  • @doug 你的代码编辑器没有给你类型并且不允许你点击一下就跳转到函数?
  • 当然。但是,当只是扫描代码屏幕时,它不会。必须将光标放在变量上。我宁愿避免这种情况。
【解决方案3】:

还有std::index_sequence 技巧(Wandbox example):

template <unsigned... i>
static constexpr auto init_axis(std::integer_sequence<unsigned, i...>) {
   return std::array{(180 + 0.1 * i)...};
};

static constexpr auto axis = init_axis(std::make_integer_sequence<unsigned, num_points>{});

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-05
    • 2012-02-10
    • 2021-03-19
    • 1970-01-01
    • 2015-01-02
    相关资源
    最近更新 更多