C++17 函数模板中的静态数组初始化 (MSVC 2019)

Posted

技术标签:

【中文标题】C++17 函数模板中的静态数组初始化 (MSVC 2019)【英文标题】:Static array initialization in a function template in C++17 (MSVC 2019) 【发布时间】:2020-02-22 14:46:45 【问题描述】:

我正在寻找一种方便有效的方法来初始化函数模板中的静态数组。例如,假设我们有一个这样的函数:


template <size_t windowSize>
int process_signal(int currentSignal)
    
    // incorrect: inits only 3 elements; want to set all values to -1
    static std::array<int, windowSize> window =  -1, -1, -1 ; 
    // do something...

这样我只能用 -1 初始化前 3 个元素,但不能全部初始化。

到目前为止,我提出的最佳解决方案是使用 constexpr 函数进行编译时初始化:

template <size_t sz, int value>
constexpr std::array<int, sz> init_array() 

    std::array<int, sz> arr;
    //arr.fill(-1); // will be constexpr since C++20

    // operator [] is constexpr in C++17
    for (int i = 0; i < sz; ++i)
        arr[i] = value;

    return arr;



template <size_t windowSize>
int process_signal(int currentSignal)
    
    // works fine, but extra variable needed
    constexpr static std::array<int, windowSize> init_value = init_array<windowSize, -1>();
    static std::array<int, windowSize> window = init_value;

    // window will be updated...


这样我可以在编译时初始化一次数组。但是,它需要一个额外的constexpr 变量(在我的情况下为init_value)来绑定init_array() 函数的结果。如果我在没有它的情况下尝试相同的操作,init_array() 将作为普通函数调用(因此初始化不再是编译时):

template <size_t windowSize>
int process_signal(int currentSignal)
    
    // no longer compile-time in MSVC2019
    static std::array<int, windowSize> window = init_array<windowSize, -1>();

    // window will be updated...

如果没有额外的constexpr 变量,是否可以做同样的事情?或者,也许它只是特定于我的编译器(MSVC 2019)?

【问题讨论】:

你可以使用模板参数包扩展,这里是some inspiration(方法3)。 这能回答你的问题吗? How to initialize all members of an array to the same value? -> This answer @zett42 在这个问题中,我想强调使用 constexpr 函数初始化普通变量与 MSVC 编译器中的 constexpr 变量之间的区别。 【参考方案1】:

不需要你的额外变量,只需写:

static auto window = init_array<windowSize, -1>();

初始化器仍然是一个常量表达式,应该由编译器在编译时计算。

如果您的编译器由于某种原因没有正确优化它,那么您可以使用 lambda 来存储中间 constexpr 变量:

static auto window = [] constexpr auto x = init_array<windowSize, -1>(); return x; ();

或者你可以把它放在一个单独的函数中,例如在init_array:

template <size_t sz, int value>
constexpr std::array<int, sz> init_array() 

    constexpr auto x = []
        std::array<int, sz> arr;
        //arr.fill(-1); // will be constexpr since C++20

        // operator [] is constexpr in C++17
        for (int i = 0; i < sz; ++i)
            arr[i] = value;

        return arr;
    ();
    return x;

您可以使函数更加通用:

template <std::size_t S, typename T>
constexpr auto init_array(const T& value) 

    return std::apply([&](auto... pack)
        return std::array((void)pack, value)...;
    , std::array<int, S>);

用作

static auto window = init_array<windowSize>(-1);

template <std::size_t S, auto value>
constexpr auto init_array() 

    constexpr auto x = std::apply([](auto... pack)
        return std::array((void)pack, value)...;
    , std::array<int, S>);
    return x;

用作

static auto window = init_array<windowSize, -1>;

强制编译时评估(直到复制)。

【讨论】:

这是我在帖子中写的:这种方式初始化在 MSVC 2019 中不再是编译时 @mentalmushroom 编译器决定如何处理它。将 constexpr 值复制到静态变量中可能比运行简单函数更耗时。 @mentalmushroom 我不知道为什么 MSVC 没有正确优化这个,但我添加了一个替代方案。 @mentalmushroom 在查看了 MSVC 生成的程序集之后,我会说这是一个错过的优化。如果初始化程序是一个更复杂的常量表达式,MSVC 似乎没有认识到它可以执行常量初始化而不是 static 的动态初始化。没有办法要求常量初始化,如果你使用initial_valueconstexpr,它恰好可以工作。这种优化取决于编译器。

以上是关于C++17 函数模板中的静态数组初始化 (MSVC 2019)的主要内容,如果未能解决你的问题,请参考以下文章

MSVC 导致静态 const 模板成员初始化失败

GCC C++14/17 成员函数指针模板参数的区别

为啥 C++17 中的全局内联变量和静态内联成员需要守卫?

局部静态的线程安全初始化:MSVC [重复]

MSVC C++ 17 获得词法规范化路径

如何通过可变参数模板将多个构造函数参数转发到数组初始值设定项列表?