【问题标题】:Legitimate to initialize an array in a constexpr constructor?在 constexpr 构造函数中初始化数组是否合法?
【发布时间】:2020-04-26 23:11:08
【问题描述】:

下面的代码合法吗?

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_sequences。

【问题讨论】:

  • Gcc10 也接受它。
  • 你能从 MSVC 抄录错误吗?
  • ...还有 GCC。
  • @songyuanyao - g++10 接受它编译 C++20;拒绝它编译 C++17 或更早版本;重点似乎应该在初始化列表中初始化_v,直到 C++17。也许在 C++20 中有所改变。
  • @Evg 这实际上很有趣,因为它可能表明 Clang 使用它的“意识”,即静态存储持续时间对象被归零以说“好吧,这个对象可能已经默认初始化,但读取从其int 成员将永远不会有未定义的行为”。我想知道 GCC not 这样做是否合规,或者相反......

标签: c++ template-meta-programming constexpr compile-time-constant


【解决方案1】:

constexpr 上下文 until C++20 中禁止进行简单的默认初始化。

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

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

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

【讨论】:

  • @max66 我也是!我所做的只是扫描 Wikipedia 上的 C++20 更改列表,找到与 constexpr 相关的内容,然后浏览链接的提案;)
  • 糟糕的是,我使用 C++ 已有 20 多年了。如果我每天都学到新东西……或者我是一个糟糕的程序员,或者 C++ 变得太复杂了。
  • @max66 几乎可以肯定是后者。此外,它每隔几年就会发生根本性变化,这一事实使其成为一个快速移动的目标。谁能跟得上?!甚至编译器也跟不上。
  • @max66 想到这篇论文:Remember the Vasa!
  • @max66 我认为这是因为 C++ 太复杂了。除了委员会成员之外,没有人知道 C++ 的一切。即使是那些人也可能会错过一两件事……
【解决方案2】:

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

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;

【讨论】:

  • 零初始化是 C++17 的方式(自从我发布问题后我就学会了)。令我惊讶的是,您的第二段代码被 GCC 接受了。无论如何,让它被所有人接受的方法是在构造函数中初始化基类:constexpr foo() : std::array&lt;int, N&gt;() {…}.
  • 第二个代码 sn-p 试图找到一种方法来初始化数组,而无需(不必要地)首先对其进行零初始化。它似乎有效,但我忘了用它 Clang 测试它:D
猜你喜欢
  • 2020-09-30
  • 1970-01-01
  • 2011-05-02
  • 1970-01-01
  • 2014-10-04
  • 1970-01-01
  • 1970-01-01
  • 2017-05-16
  • 1970-01-01
相关资源
最近更新 更多