Search code examples
c++lambdac++20trailing-return-type

is lambda capture allowed in c++20 function trailing return type and noexcept operator?


Simple code as below or godbolt has different results from gcc, clang and Visual Studio.

auto foo1(int n) -> decltype(([n]() { return n+1; }()))
// gcc error: use of parameter outside function body before '+' token
{
    return [n]() { return n+1; }();
}

auto foo2(int n) -> decltype(([&n]() { return n+1; }()))
// gcc error: use of parameter outside function body before '+' token
{
    return [&n]() { return n+1; }();
}

auto foo3(int n) -> decltype(([&]() { return n+1; }()))
// gcc error: use of parameter outside function body before '+' token
// gcc and clang error: non-local lambda expression cannot have a capture-default
// VS2022 17.1 warning C5253: a non-local lambda cannot have a capture default
{
    return [&]() { return n+1; }();
}

It seems that gcc does not allow any type of capture if the lambda is in function trailing return type (or noexcept(noexcept(...))). clang is OK with capture by value and capture by reference but not OK with capture default. Visual Studio up to 17.0 allows any type of capture. Visual Studio 17.1 gives a warning for capture default.

What is the correct behavior here? It seems gcc, clang and latest VS 17.1 all agree capture default is not allowed in trailing return type (or noexcept(noexcept(...))). But is it a correct design? It is very convenient to put the same expression in return statement, noexcept and trailing return type (often by a macro) if the function is one-liner. But it is not possible now if there is a lambda with capture (default?) in this expression.


Solution

  • From [expr.prim.lambda.capture]/3:

    A lambda-expression shall not have a capture-default or simple-capture in its lambda-introducer unless its innermost enclosing scope is a block scope ([basic.scope.block]) or it appears within a default member initializer and its innermost enclosing scope is the corresponding class scope ([basic.scope.class]).

    Which means that captures such as [n], [&n] or [&] are not allowed in trailing return types or noexcept specifiers, but initialized captures such as [i = 1] are.

    So GCC is right to reject the first two function definitions and GCC and Clang are right to reject the last one.