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
14bool 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
13bool 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
8void 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
11void 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
6void 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
11void 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
8void 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 beconst
!- 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 variable1
2
3
4
5
6
7
8
9
10
11
12void 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?”
- “Does it make sense to expose
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