Lambdas, local types, and global namespace

2019-06-17 18:43发布

问题:

This minimal program

template <typename X>
void foo (X x)
{
    bar (x);
}

template <typename X>
void bar (X x)
{
}

int main ()
{
    foo ([]{});
}

compiles with gcc (4.8.5 and 5.3) and fails to compile with clang (3.7)

My analysis is as follows.

bar is used in foo and declared after foo, so it is not visible at foo definition point. The only way bar can be found at foo instantiation point is via argument-dependent lookup.

The only argument to both foo and bar is a lambda defined in main.

Apparently gcc regards its type as declared in the global namespace, while clang does not. Thus, gcc can find bar via ADL and clang cannot.

Same thing happens when we use a type defined locally in main:

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

It looks like gcc is in the wrong here. The type of the lambda according to the standard is unnamed (expr.prim.lambda/3), so it should not belong to any namespace. The local type supposedly shouldn't belong to the global namespace either.

Is the analysis correct? Is this a known gcc bug?

This question is inspired by this question.

回答1:

GCC is correct, per the resolution of DR1690/1691.

[expr.prim.lambda]/4:

The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression. [ Note: This determines the set of namespaces and classes associated with the closure type ([basic.lookup.argdep]). The parameter types of a lambda-declarator do not affect these associated namespaces and classes. — end note ]

[basic.lookup.argdep]/2:

If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the innermost enclosing namespaces of its associated classes.

The innermost enclosing namespace of the closure type at issue is the global namespace, so the global namespace is an associated namespace.



回答2:

GCC is wrong here. It finds bar() via ADL even though []{} is not a member of the global namespace. Using the same quote T.C. used:

[expr.prim.lambda]/4:

The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression. [ Note: This determines the set of namespaces and classes associated with the closure type ([basic.lookup.argdep]). The parameter types of a lambda-declarator do not affect these associated namespaces and classes. — end note ]

It's easy to see this by intentionally introducing an error. In 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()>')

On the other hand, if we move the lambda declaration to global scope, Clang does not complain:

auto f = []{};

int main ()
{
    foo (f);
}

FWIW this was reported as Bug 57433 for GCC but it's unconfirmed. It contains more examples of programs in which GCC accepts/Clang rejects.