在模板lambda中隐式捕获const变量,而未指定捕获默认值

考虑以下代码:

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

GCC 和 MSVC 编译得很好,但 Clang 拒绝了它。我应该信任哪个编译器?那是 Clang 尚未实现的编译器扩展还是只是 Clang 错误?

回答

这是一个叮当声错误。

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

如果满足以下条件,本地实体可在范围内使用 odr:

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

如果本地实体在其不可 odr 可用的范围内被 odr 使用,则程序格式错误。

在我们的例子中:

x由于不捕获的介入范围x(既不是简单捕获也不是捕获默认值),因此在 lambda 主体中不可 odr 使用。

所以,它不是 odr 可用的。但它是 odr 使用的吗?不,来自[basic.def.odr]/4:

如果表达式是表示变量的 id 表达式,则该变量由表达式命名。x名称显示为潜在求值表达式的变量E被 odr 使用,E除非

  • x 是可用于常量表达式 ([expr.const]) 的引用,或
  • x是非引用类型的变量,可用于常量表达式并且没有可变子对象,并且E是非 volatile 限定的非类类型表达式的潜在结果集的元素,左值到 -应用右值转换 ([conv.lval]),或
  • x是非引用类型的变量,并且E是未应用左值到右值转换的丢弃值表达式 ([expr.prop]) 的潜在结果集的元素。

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

所以x不是 odr-usable,也不是 odr-used,所以这里没有问题。


事实上,我们甚至在[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 错误。

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

如果本地实体在其不可 odr 可用的范围内被 odr 使用,则程序格式错误。

值得注意的是,规则说“如果你使用 odr,它的格式是错误的”,而不是“如果我们不能确定它是否是一个 odr 使用,它的格式错误”。

x该函数调用运算符模板的任何专业化中都没有 odr-use of 。


以上是在模板lambda中隐式捕获const变量,而未指定捕获默认值的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>