Lambdas、本地类型和全局命名空间

Lambdas, local types, and global namespace

本文关键字:全局 命名空间 类型 Lambdas      更新时间:2023-10-16

这个最小程序

template <typename X>
void foo (X x)
{
    bar (x);
}
template <typename X>
void bar (X x)
{
}
int main ()
{
    foo ([]{});
}

使用gcc(4.8.5和5.3)编译,但无法使用clang(3.7)编译

我的分析如下。

barfoo中使用,在foo之后声明,因此在foo定义点不可见。在foo实例化点可以找到bar的唯一方法是通过依赖于参数的查找。

foobar的唯一自变量是在main中定义的lambda。

显然,gcc将其类型视为在全局命名空间中声明的,而clang则不然。因此,gcc可以通过ADL找到bar,而clang不能。

当我们使用main:中本地定义的类型时,也会发生同样的情况

int main ()
{
    struct K{};
    foo (K());     // gcc compiles, clang complains
}

看起来gcc错了。根据标准,lambda的类型为未命名(expr.prim.lambda/3),因此它不应属于任何命名空间。本地类型也不应该属于全局命名空间。

分析正确吗?这是一个已知的gcc错误吗?

这个问题的灵感来源于这个问题。

GCC根据DR1690/1691的决议是正确的。

[expr.prim.lambda]/4:

闭包类型是在最小的块作用域、类作用域中声明的,或包含相应lambda表达式的命名空间范围。[注意:这确定了一组关联的命名空间和类具有闭包类型(〔basic.lookup.argdep〕)lambda声明符不会影响这些关联的命名空间和类。--尾注]

[basic.lookup.argdep]/2:

如果T是类类型(包括并集),则其关联类为:阶级本身;其所属类别(如有);及其直接和间接基类。其关联的命名空间是其关联类的最内部封闭命名空间。

所讨论的闭包类型的最里面的封闭命名空间是全局命名空间,因此全局命名空间是关联的命名空间。

GCC在这里是错误的。它通过ADL找到bar(),即使[]{}不是全局名称空间的成员。使用与T.C.相同的报价:

[expr.prim.lambda]/4:

闭包类型是在最小的块作用域、类作用域中声明的,或包含相应lambda表达式的命名空间范围。[注意:这确定了一组关联的命名空间和类具有闭包类型(〔basic.lookup.argdep〕)lambda声明符不会影响这些关联的命名空间和类。--尾注]

通过故意引入错误很容易看出这一点。在GCC中:

auto f = -[]{};
int main ()
{
    foo (f);
}
error: no match for 'operator-' (operand type is '<lambda()>')
int main ()
{
    foo (-[]{});
}
no match for 'operator-' (operand type is 'main()::<lambda()>')

另一方面,如果我们将lambda声明转移到全局范围,Clang不会抱怨:

auto f = []{};
int main ()
{
    foo (f);
}

FWIW这被报道为GCC的Bug 57433,但尚未得到证实。它包含了更多GCC接受/Clang拒绝的程序示例。