【问题标题】:GCC and Clang disagree about C++17 constexpr lambda capturesGCC 和 Clang 不同意 C++17 constexpr lambda 捕获
【发布时间】:2017-06-06 09:35:35
【问题描述】:

考虑这个例子,它将一个变量声明为 constexpr,通过在 lambda 中复制来捕获它,并声明另一个 constexpr 变量,该变量是 constexpr 函数从原始变量中解包非类型模板参数的结果。

#include <utility>

template<int I>
constexpr auto unwrap(std::integral_constant<int, I>) {
  return I;
}

int main() {
  constexpr auto i = std::integral_constant<int, 42>{};
  constexpr auto l = [i]() {
    constexpr int x = unwrap(i);
  };
}

Clang(主干)接受此代码。 (wandbox)

GCC(主干)失败并显示以下错误消息 (wandbox):

lambda_capture.cpp:11:31: error: the value of ‘i’ is not usable in a constant expression
     constexpr int x = unwrap(i);
                               ^
lambda_capture.cpp:10:28: note: ‘i’ was not declared ‘constexpr’
   constexpr auto l = [i]() {

哪个编译器是正确的?在我看来,这是一个 GCC 错误,其中 lambda 捕获的 constexpr-ness 没有正确传播到 lambda 上下文。

【问题讨论】:

  • 考虑到 C++17 不是一个东西,但我不会说它们中的任何一个是对还是错。
  • 同意@freakish,编译器可能会合理地犹豫要实现任何尚未具体设置的东西。
  • C++17 标准草案在 final stages of standardization 中,GCC 和 Clang 都声称是 language-feature complete。虽然还为时尚早,但我认为询问编译器如何实现新标准的问题是合理的,并且有助于编译器开发人员讨论事情最终应该如何工作。
  • 我知道it 与 c++17 无关,但可能会有所帮助

标签: c++ lambda c++17


【解决方案1】:

两种实现都存在错误,但我倾向于认为 GCC 在这里得到了正确的答案。


删除i 的捕获会导致 Clang 拒绝编译代码。这意味着它显然在某个地方存在错误。

[expr.const]/2.12:

表达式e 是核心常量表达式,除非求值 的e,遵循抽象机的规则,将评估 以下表达式之一:

  • [...]
  • lambda-expression 中,对 [...] 一个变量的引用,该变量在 lambda-expression 之外定义了自动存储持续时间, 其中引用将是 odr 使用;
  • [...]

Clang 的行为是精神分裂的:如果在 body 中使用 i 不是 odr-use,那么它不需要被捕获,但如果显式捕获被删除,它会拒绝 OP 中的代码; OTOH,如果是odr-use,那么上面的unwrap(i)不是一个常量表达式,所以它应该拒绝x的初始化。


GCC 的 lambda 实现在 odr 使用方面非常糟糕。它会在超早期进行不断折叠,从而导致各种微妙的恶作剧。另一方面,对于显式捕获,它会转换所有用途,无论它是否实际上是 odr 用途。积极的常量折叠意味着如果i 的捕获被删除,它就会接受 OP 的代码。

假设 unwrap(i) 确实 odr-use i,那么根据 [expr.const]/2.12,OP 的代码格式错误是正确的。


unwrap(i) 是否真的 odr 使用 i?这个问题boils down 是否复制初始化unwrap 的参数对象算作对i 应用左值到右值转换。我在标准中没有看到任何明确说明此处应用左值到右值转换的内容,而是[dcl.init]/17.6.2 表示我们调用构造函数(在这种情况下,是简单的隐式定义的复制构造函数)传递@987654337 @ 作为参数绑定到其参数,引用绑定是 odr-use 的经典示例。

可以肯定的是,应用从左到右的转换会导致从 i 复制初始化 integral_constant&lt;int, 42&gt; 对象,但这里的问题是,标准中没有任何内容相反 - 所有复制- 对来自iintegral_constant&lt;int, 42&gt; 对象的初始化算作从左到右的转换。

【讨论】:

  • 所以你的意思是即使隐式复制构造函数是 constexpr,它仍然算作 i 的 odr 使用,并且闭包类型是核心常量表达式。这令人沮丧,特别是因为听起来 GCC 可以接受 i 而没有捕获也是一个缺陷,因为我希望能够在 constexpr lambda 中使用在 lambda 之外定义的 constexpr 变量。您是否认为标准中的措辞不一致,或者您认为这样写是有原因的?
  • 似乎删除捕获会反转两个编译器的行为。
  • 这些有错误报告吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-05-28
  • 1970-01-01
  • 2020-11-07
  • 2019-06-06
  • 1970-01-01
  • 1970-01-01
  • 2021-10-05
相关资源
最近更新 更多