Lambda捕获和具有相同名称的参数-谁遮蔽了另一个?(c声与gcc)


125
auto foo = "You're using g++!";
auto compiler_detector = [foo](auto foo) { std::puts(foo); };
compiler_detector("You're using clang++!");
  • clang ++ 3.6.0及更高版本打印出“您正在使用clang ++!” 并警告捕获 foo未使用。

  • g ++ 4.9.0及更高版本打印出“您正在使用g ++!” 并警告未使用该参数 foo

哪种编译器更准确地遵循C ++标准?

魔盒示例


1
将代码从wandbox粘贴到此处(它们似乎已经忘记了共享按钮),使得VS2015(?)似乎同意clang并警告C4458:声明为'foo'隐藏类成员
nwp

12
很好的例子..
deviantfan '17

4
lambda具有带有模板函数调用运算符的类型,因此逻辑使我说该参数应像在中那样遮盖捕获的变量struct Lambda { template<typename T> void operator()(T foo) const { /* ... */ } private: decltype(outer_foo) foo{outer_foo}; }
skypjack

2
@nwp VS是错误的,lambda的数据成员未命名,因此无法被遮盖。该标准说“将对捕获实体的访问转换为对相应数据成员的访问”,这使我们处于平方。
n。代词

10
我希望clang版本是正确的-如果函数外部的某些东西遮盖了函数参数,那么它将开创一个新局面,而不是反过来!
MM

Answers:


65

更新:正如核心主席在底引号中所承诺的那样,代码现在格式不正确

如果一个标识符在一个简单的捕捉显示为说明符-ID的的参数的λ-说明符参数声明子句,是形成不良的节目。


不久前,有一些关于在lambda中查找名称的问题。他们由N2927解决:

新的措辞不再依赖于查找来重新映射捕获的实体的使用。它更清楚地否认以下解释:lambda的复合语句是在两次通过中处理的,或者该复合语句中的任何名称都可以解析为闭包类型的成员。

查找总是在lambda-expression的上下文中完成的,永远不要在转换为闭包类型的成员函数体之后的“之后”。参见[expr.prim.lambda] / 8

所述λ-表达化合物语句产生的功能体的函数调用操作的([dcl.fct.def]),但对于名称查找的目的,[...],该化合物语句中的上下文中考虑所述λ-表达。[ 示例

struct S1 {
  int x, y;
  int operator()(int);
  void f() {
    [=]()->int {
      return operator()(this->x+y);  // equivalent to: S1::operator()(this->x+(*this).y)
                                     // and this has type S1*
    }; 
  }
};

结束示例 ]

(该示例还表明,查找不会以某种方式考虑生成的闭合类型的捕获成员。)

该名称foo未在捕获中(重新)声明;它在包含lambda表达式的块中声明。参数foo在嵌套在该外部块中的块中声明(请参阅[basic.scope.block] / 2,其中也明确提到了lambda参数)。查找的顺序显然是从内部到外部块。因此,应该选择参数,即Clang是正确的。

如果您要使捕获成为init-capture,即foo = ""代替foo,答案将不清楚。这是因为捕获实际上会引起一个声明该声明的“ block”没有给出。我就此向核心主席发了信息,他回答

这是问题2211(一个新的问题列表将很快出现在open-std.org网站上,不幸的是,仅占位符解决了许多问题,其中一个是问题;我正在努力填补科纳之前的空白在月底开会)。CWG在我们一月份的电话会议上讨论了此问题,如果捕获名称也是参数名称则方向是使程序格式错误。


我在这里什么也没拆:) 简单捕获什么也不声明,因此名称查找的正确结果是显而易见的(顺便说一句,如果您使用默认捕获而不是显式捕获,GCC会正确处理)。init-capture有点棘手。
TC

1
@TC我同意。我提出了一个核心问题,但显然已经进行了讨论,请参见编辑后的答案。
哥伦布

6

我正在尝试对问题进行一些评论,以便为您提供有意义的答案。
首先,请注意:

  • 为每个复制捕获变量的lambda声明了非静态数据成员
  • 在特定情况下,lambda具有闭包类型,该闭包类型具有接受参数名为的公共内联模板函数调用运算符 foo

因此,逻辑使我乍一看就知道该参数应该像所捕获的那样隐藏捕获的变量:

struct Lambda {
    template<typename T> void operator()(T foo) const { /* ... */ }
    private: decltype(outer_foo) foo{outer_foo};
};

无论如何,@ nm正确地指出了为复制捕获变量声明的非静态数据成员实际上是未命名的。话虽如此,未命名的数据成员仍然可以通过标识符(即foo)进行访问。因此,函数调用运算符的参数名称仍应(让我说)遮盖该标识符
正如@nm在问题注释中正确指出的:

原始捕获的实体应根据范围规则正常阴影

因此,我会说c是正确的。


如上所述,在此上下文中的查找永远不会像在转换后的闭合类型中那样执行。
哥伦布

@Columbo我添加了我错过的那一行,即使从推理中很明显,也就是clang是正确的。有趣的是,我在尝试给出答案时发现了[expr.prim.lambda] / 8,但我却无法像您一样正确使用它。这就是为什么每次很高兴阅读您的答案的原因。;-)
skypjack
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.