CodeNebula
C++23coreexpert

Lambda Trailing Return Type Scope Changes

C++23 · Completing Modern · Compiler flag: -std=c++23

Explanation

Name lookup in a lambda's trailing return type now sees the lambda's own captures and parameters, fixing a longstanding inconsistency.

Real-World Scenario

Use a captured variable's type in the lambda's return type declaration without workarounds.

Quick Snippet

snippet.cpp
auto make_adder(int x) {
    return [x](int y) -> decltype(x + y) { return x + y; };
}

Full Example

lambda_return_scope.cpp
// CodeNebula - C++23 Example: Lambda Trailing Return Type Scope
// Compile: g++ -std=c++23 -Wall -Wextra -o example lambda_return_scope.cpp
// Note: Requires GCC 14+ / Clang 17+ / MSVC 19.36+

// C++23 changes the scope of the trailing return type in lambdas.
// Previously, the trailing return type could not see the lambda's own captures.
// Now, the trailing return type is evaluated in the lambda's scope.
// This fixes a long-standing quirk where decltype on captured variables failed.

#include <iostream>
#include <string>
#include <type_traits>
#include <vector>
#include <numeric>

int main() {
    std::cout << "=== Lambda Trailing Return Type Scope (C++23) ===" << std::endl;

    // C++23: trailing return type can reference captured variables
    int x = 42;
    // Before C++23, this could fail because 'x' wasn't in scope
    // for the trailing return type. Now it works.
    auto get_ref = [&x]() -> decltype(x)& {
        return x;
    };
    get_ref() = 100;
    std::cout << "Modified via lambda ref: x = " << x << std::endl;

    // Practical example: return type depends on captured variable type
    std::string name = "C++23";
    auto make_greeting = [&name]() -> decltype(name + "!") {
        return "Hello, " + name + "!";
    };
    std::cout << "Greeting: " << make_greeting() << std::endl;

    // Demonstrating with init-capture
    std::cout << "\n=== Init-capture in trailing return type ===" << std::endl;
    auto factory = [val = std::vector<int>{1, 2, 3}]()
        -> decltype(val)  // C++23: 'val' visible in return type
    {
        return val;
    };
    auto result = factory();
    std::cout << "Factory produced: ";
    for (int v : result) std::cout << v << " ";
    std::cout << std::endl;

    // Type deduction with captured variable
    std::cout << "\n=== Type-dependent return ===" << std::endl;
    double pi = 3.14159;
    auto doubler = [&pi]() -> decltype(pi * 2) {
        return pi * 2;
    };
    std::cout << "Doubled pi: " << doubler() << std::endl;
    std::cout << "Return is double: "
              << std::is_same_v<decltype(doubler()), double> << std::endl;

    // Combining with generic lambdas
    std::cout << "\n=== Generic lambda with scope-aware return ===" << std::endl;
    std::vector<int> data = {1, 2, 3, 4, 5};
    auto summer = [&data](auto init) -> decltype(init) {
        return std::accumulate(data.begin(), data.end(), init);
    };
    std::cout << "Sum (int):    " << summer(0) << std::endl;
    std::cout << "Sum (double): " << summer(0.0) << std::endl;

    return 0;
}

/*
Expected output:
=== Lambda Trailing Return Type Scope (C++23) ===
Modified via lambda ref: x = 100
Greeting: Hello, C++23!

=== Init-capture in trailing return type ===
Factory produced: 1 2 3

=== Type-dependent return ===
Doubled pi: 6.28318
Return is double: 1

=== Generic lambda with scope-aware return ===
Sum (int):    15
Sum (double): 15
*/
Run command
g++ -std=c++23 -Wall -Wextra -o example lambda_return_scope.cpp