编译时算法初始化std::array

40

考虑:

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函数。


2
是的,请参见 https://dev59.com/YbTma4cB1Zd3GeqP2jFt#56376301 - JVApen
1
如果您的数据确实是只读的,并且采用此模式,在大多数硬件上,对于大多数用例,最好在运行时计算它。7810 * 8字节对于数组来说是一个很大的缓存占用量。加载基址+比例因子仅为2个双精度= 16字节。在运行时计算是便宜的:一个int-> FP转换和一个FMA或mul + add(加上加载常量)。因此,是的,在高速缓存命中的情况下,LUT更快,但特别是在循环内重复使用时,仅计算通常会很好。 - Peter Cordes
3个回答

43

为了完整起见,这里有一个版本,它不需要定义函数,而是使用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;
}();

1
你在lambda中不需要使用constexpr :) - Rakete1111
2
@Rakete1111 你是对的。Lambda的调用运算符会自动标记为constexpr - Nikos C.

32

以下是完整可编译代码:

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

3
建议用std::array<double,num_points>代替auto,这样读者就不必检查初始化函数来确定类型。 - doug
4
我认为这是个人偏好的问题。有些人喜欢知道类型,有些人则更喜欢少打几个字母(语言游戏)。 - SergeyA
我也喜欢自动类型推断,出于同样的原因。但是当类型对于阅读代码的人不明显时,包括一个月后的我在内,我会进行额外的输入。这对于后来者来阅读问题也可能有所帮助。 - doug
1
@doug,你的代码编辑器没有提供类型,并且不能让你在单击一下后跳转到函数吗? - SergeyA
当然可以。但是仅仅扫描代码屏幕是不够的,必须将光标放在变量上。我更喜欢避免这种情况。 - doug

16

还有一种方法叫做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>{});

这在C++14中是否可行? - undefined
@jp94 是的。唯一不在C++14中的部分是CTAD: https://wandbox.org/permlink/q9aolw8WgWr0tFRF - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接