在没有指定capture-default的情况下隐式捕获模板lambda中的const变量



考虑以下代码:

auto f() {
const auto x = 1;
return [] (auto) { return x; };
}

GCC和MSVC可以正常编译,但是Clang拒绝了。我应该信任哪个编译器?是Clang还没有实现的编译器扩展还是Clang的bug?

这是一个clang bug。

我们的规则是[basic.def.odr]/9:

本地实体在作用域中是不可用的,如果:

  • 本地实体不是*this,或者存在封闭类或非lambda函数参数作用域,如果最内层的这种作用域是函数参数作用域,则它对应于非静态成员函数,并且
  • 对于引入实体的点和作用域(其中*this被认为是在最内层的封闭类或非lambda函数定义作用域内引入的)之间的每个中间作用域([basic.scope.scope]),要么:
    • 中间作用域是块作用域,或者
    • 中间作用域是具有简单捕获命名实体或具有默认捕获的lambda表达式的函数参数作用域,lambda表达式的块作用域也是中间作用域。

如果一个局部实体在其不可用的作用域中被odr使用,则该程序是病态的。

在我们的例子中:

auto f() {
const auto x = 1;
return [] (auto) { return x; };
}

x在lambda体中是不可用的,因为中间作用域没有捕获x(既不是简单捕获也不是默认捕获)。

所以,它是不可用的。但它是常用的吗?没有,from [basic.def.odr]/4:

如果表达式是表示变量的id-表达式,则由表达式命名。除非

,否则名称显示为潜在求值表达式E的变量x将不被E使用。
  • x是可用于常量表达式([expr.const])或
  • 的引用。
  • x是非引用类型的变量,可用于常量表达式并且没有可变的子对象,E是应用左值到右值转换([conv.lval])的非易失性限定非类类型表达式的潜在结果集的元素,或者
  • x是非引用类型的变量,E是不应用左值到右值转换的丢弃值表达式([expr.prop])的潜在结果集合中的一个元素。

应用第二个项目符号(我们的变量甚至被命名为x!)x在常量表达式中可用,因为它是一个常量整型,而E在这里是左值到右值的转换。

所以x不是odr可用的,但也不是odr使用的,所以这里没有问题。


事实上,我们甚至在[expr.prim.lambda.capture]/7:

中有这个例子。
void f(int, const int (&)[2] = {});         // #1
void f(const int&, const int (&)[1]);       // #2
void test() {
const int x = 17;
auto g = [](auto a) {
f(x);                       // OK: calls #1, does not capture x
};
}

是的,Clang bug。

适用的规则来自[basic.def.odr]/9:

如果一个局部实体在其不可用的作用域中被odr使用,则该程序是病态的。

值得注意的是,规则说的是"如果你过度使用它,它就是格式不良的",而不是"如果我们不能确定它是否是过度使用,它就是格式不良的"。

在该函数调用操作符模板的任何专门化中都不允许使用x