0%

Lambdas as Local Function in C++

This is my learning notes from the course - Mastering C++ Standard Library Features.

Lambdas as Local Functions

  • Use cases for lambdas as local functions
  • IIEF idiom

Why Lambdas as Local Functions?

  • Reduce code repetition without exposing additional functions
  • Achieve const correctness with the IIFE idiom

Example: Error Handling

  • Without the lambda as a local function:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    bool something::initialize()
    {
    if(!init_step0())
    {
    std::cerr << "Step 0 failed\n";
    return false;
    }

    if(!init_step1())
    {
    std::cerr << "Step 1 failed\n";
    return false;
    }
    }
  • With the lambda as a local function:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    bool something::initialize()
    {
    const auto try_setup = [](const char* name, auto f)
    {
    if(f()) return true;
    std::cerr << name << " failed\n";
    return false;
    };

    return try_setup("step0", [&]{ return init_step0(); })
    && try_setup("step1", [&]{ return init_step1(); })
    && try_setup("step2", [&]{ return init_step2(); });
    }

Example: Avoiding Repetition

  • Without the lambda as a local function:

    1
    2
    3
    4
    5
    6
    7
    8
    void something::print_status()
    {
    std::cout << _progress << ' ';
    if(_name != nullptr) { std::cout << *_name << ' '; }
    if(_type != nullptr) { std::cout << *_type << ' '; }
    if(_url != nullptr) { std::cout << *_url << ' '; }
    std::cout << _action << ' ';
    }
  • With the lambda as a local function:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void something::print_status()
    {
    auto field = [](auto&& x){ std::cout << x << ' '; };
    auto nullable = [](auto&& x){ if(x) field(x); };

    field(_progress);
    nullable(_name);
    nullable(_type);
    nullable(_url);
    field(_action);
    }

Example: Reusing Logic for Different Types

  • Without the lambda as a local function:

    1
    2
    3
    4
    5
    6
    void person::load_from(const json& j)
    {
    _name = j.has("name") ? j["name"].as<std::string>() : "anonymous";
    _age = j.has("age") ? j["age"].as<int>() : 0;
    _city = j.has("city") ? j["city"].as<city_id>() : city_id::unknown;
    }
  • With the lambda as a local function:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void person::load_from(const json& j)
    {
    auto set_or = [&](auto& dst, const char* key, auto def)
    {
    dst = j.has(key) ? j[key].as<decltype(dst)> : def;
    };

    set_or(_name, "name", "anonymous");
    set_or(_age, "age", "0");
    set_or(_city, "city", "city_id::unknown");
    }

IIFE Idiom - The Problem

  • Initializing a local variable can sometimes depend on complicated control flow
    1
    2
    3
    4
    5
    6
    7
    8
    void update()
    {
    int precision = 20;
    if(some_condition && bar()) { precision += inc(); }
    else if(baz()) { precision -= 5; }

    render(precision);
    }
  • precision is set at the beginning and never mutated again - it should be const!
  • Expressing the control flow as a single expression is either impossible or harms readability

That’s why IIFE idiom comes to play.

  • IIFE stands for “Immediately Invoked Function Expression”
  • The idea is to wrap the complex initialization logic in a lambda expression which is immediately invoked
  • The result of the lambda expression can then be stored in a const local variable
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    void update()
    {
    const int precision = []
    {
    int result = 20;
    if(some_condition && bar()) { result += inc(); }
    else if(baz()) { result -= 5; }
    return result;
    }();

    render(precision);
    }

When to Use Lambdas as Local Functions?

  • Identity repetition or common patterns inside a function
  • Extract the common functionality to a local lambda l
  • Depending on the reusability, terseness, and complexity of l, ask yourself:
    • “Does it make sense to expose l as a free/member function?”
    • “Is l complex enough that it needs to be unit tested?”
    • “Does l negatively impact readability of the function?”

Summary

  • Learned that lambdas can be used as local functions to avoid repetition and increase readability
  • Understood that the IIFE idiom can help restore const -correctness and prevent mistakes related to complicated initialization logic
  • Learned that depending on the scope of the refactor, the complexity/readability of the lambda, and its reusability/testability - consider using a proper function instead