GCC和Clang关于C ++ 17 constexpr lambda捕获存在分歧 [英] GCC and Clang disagree about C++17 constexpr lambda captures

查看:91
本文介绍了GCC和Clang关于C ++ 17 constexpr lambda捕获存在分歧的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑以下示例,该示例将变量声明为constexpr,在lambda中通过拷贝捕获它,并声明另一个constexpr变量,这是constexpr函数从原始变量解开非类型模板参数的结果。

Consider this example which declares a variable as constexpr, captures it by copy in a lambda, and declares another constexpr variable which is the result of a constexpr function unwrapping a non-type template parameter from the original variable.

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

C语(干线)接受此代码。 ( wandbox

Clang (trunk) accepts this code. (wandbox)

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

GCC (trunk) fails with the following error message (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上下文。

Which compiler is correct? It seems to me that this is a GCC bug, where the constexpr-ness of lambda captures is not correctly propagated to the lambda context.

推荐答案

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

Both implementations are bugged, but I'm inclined to think that GCC got the right answer here.

捕获 i 会使Clang拒绝编译代码。

Dropping the capture of i causes Clang to refuse to compile the code. That means it clearly has a bug somewhere.

[expr.const] /2.12


表达式 e 是一个核心常数表达式,除非 e 的评估
按照抽象机的规则评估
之一以下表达式:

An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:


  • [...]

  • lambda表达式中,对具有自动存储持续时间的变量的引用,该变量在 lambda-expression
    之外定义,其中引用将用作odr用法;

  • [...]

  • [...]
  • in a lambda-expression, a reference to [...] a variable with automatic storage duration defined outside that lambda-expression, where the reference would be an odr-use;
  • [...]

lang语的行为是精神分裂症:如果在主体中使用 i 并非是odr-use,因此不需要捕获它,但是如果显式捕获是,它将拒绝OP中的代码。删除; OTOH,如果是odr用途,则上述 unwrap(i)不是常量表达式,因此应拒绝<$ c $的初始化c> x 。

Clang's behavior is schizophrenic: if the use of i in the body is not an odr-use, then it doesn't need to be captured, yet it rejects the code in the OP if the explicit capture is removed; OTOH, if it is an odr-use, then by the above unwrap(i) isn't a constant expression, and so it should reject the initialization of x.

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

GCC's lambda implementation is woefully bad with respect to odr-use. It does constant-folding ultra-early, resulting in all kinds of subtle mischief. On the other hand, for explicit captures it transforms all uses, whether or not it's actually an odr-use. The aggressive constant folding means that it accepts OP's code if the capture of i is removed.

假定 unwrap(i)确实使用了 i ,那么按照[expr.const] /2.12,OP是正确的代码格式错误。

Assuming that unwrap(i) does odr-use i, then it is correct that, per [expr.const]/2.12, OP's code is ill-formed.

是否 unwrap(i)实际使用 i 吗?该问题归结为以了解是否复制了-初始化 unwrap 的参数对象被视为对 i 应用左值到右值转换。我没有在标准中看到任何明确表明在此处应用从左值到右值转换的内容,而是 [dcl.init] /17.6.2 表示我们调用了一个传递 i 作为绑定到其参数的参数,引用绑定是odr使用的经典示例。

Does unwrap(i) actually odr-use i? That question boils down to whether copy-initializing the parameter object of unwrap counts as applying an lvalue-to-rvalue conversion to i. I don't see anything in the standard that explicitly says that an lvalue-to-rvalue conversion is applied here, and instead [dcl.init]/17.6.2 indicates that we call a constructor (in this case, the trivial implicitly defined copy constructor) passing i as the argument bound to its parameter, and reference binding is a classic example of odr-use.

可以肯定的是,将l应用于-r转换将导致 i integral_constant< int,42> 对象的复制初始化。这里的问题是标准中没有什么可以说相反的- i integral_constant< int,42> 对象的所有复制初始化c $ c>算作一对一转换。

To be sure, applying an l-to-r conversion would result in a copy-initialization of an integral_constant<int, 42> object from i, but the problem here is that nothing in the standard says the converse - that all copy-initializations of an integral_constant<int, 42> object from i count as l-to-r conversions.

这篇关于GCC和Clang关于C ++ 17 constexpr lambda捕获存在分歧的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆