在 constexpr 构造函数中初始化数组是否合法?

Legitimate to initialize an array in a constexpr constructor?

本文关键字:是否 数组 初始化 constexpr 构造函数      更新时间:2023-10-16

以下代码合法吗?

template <int N>
class foo {
public:
constexpr foo()
{
for (int i = 0; i < N; ++i) {
v_[i] = i;
}
}
private:
int v_[N];
};
constexpr foo<5> bar;

Clang接受它,但GCC和MSVC拒绝它。

GCC 的错误是:

main.cpp:15:18: error: 'constexpr foo<N>::foo() [with int N = 5]' called in a constant expression
15 | constexpr foo<5> bar;
|                  ^~~
main.cpp:4:15: note: 'constexpr foo<N>::foo() [with int N = 5]' is not usable as a 'constexpr' function because:
4 |     constexpr foo()
|               ^~~
main.cpp:4:15: error: member 'foo<5>::v_' must be initialized by mem-initializer in 'constexpr' constructor
main.cpp:12:9: note: declared here
12 |     int v_[N];
|         ^~

如果这种代码没问题,我可以减少很多index_sequence的使用。

constexpr上下文中禁止简单的默认初始化,直到C++20。

我猜,原因是很容易"意外"地从默认初始化的原语中读取,这种行为会给你的程序提供未定义的行为,并且具有未定义行为的表达式被直接禁止constexpr(ref(。该语言已经扩展,因此现在编译器必须检查是否发生了这样的读取,如果没有,则应接受默认初始化。对于编译器来说,这需要做更多的工作,但是(如您所见!(对程序员有实质性的好处。

本文建议允许在 constexpr 上下文中对普通的默认可构造类型进行默认初始化,同时继续禁止调用未定义的行为。简而言之,只要未初始化的值不被读取,在堆和堆栈分配的场景中,constexpr 中都应该允许这种状态。

从 C++20 开始,像您一样v_"未初始化"是合法的。然后你继续为其所有元素分配值,这很棒。

虽然这不能直接回答你的问题,但我认为至少值得一提。您可以简单地使用类内初始化并将数组零初始化:

int v_[N]{};

另一种方法,无需先初始化数组,是(私下(从std::array继承。奇怪的是,这实际上被GCC接受,但没有被Clang接受:

#include <array>
template<int N>
struct foo : private std::array<int, N> {
constexpr foo() {
for (auto i = int{}; i < N; ++i) {
(*this)[i] = i;
}
}
};
constexpr foo<5> bar;