This is my learning notes from the course - Mastering C++ Standard Library Features.
Lambdas as First-Class Citizens
- What it means to be a “first-class citizen“
- How lambdas replace old Standard Library utilities
- Templates and lambda expressions
- Type-erasure for lambdas:
std::function
- Higher-order functions
Lambdas: Versatile Tools
- Meaning of “first-class citizen“
- Standard Library utilities replaced by lambdas
Lambdas: First-Class Citizens
In programming language design, a first-class citizen (also type, object, entity, or value) in a given programming language is an entity which supports all the operations generally available to other entities. These operations typically include being passed as an argument, returned from a function, modified, and assigned to a variable.
– from Wikipedia - First-class citizen
In C++, regular functions are not first-class citizens.
1
2
3
4void foo()
{
void bar(){ } // ERROR: nested function
}Instead, if you use a lambda, you are able to do it and assign it to a variable.
An Example of invoking the
std::find_if
algorithm by passing a regular function.- Works, because
predicate
is equivalent to&predicate
1
2
3
4
5bool predicate(const Foo&);
void foo()
{
std::find_if(c.begin(), c.end(), predicate);
}
- Works, because
Pointers to functions can be invoked with
( )
1
2
3f_ptr();
// is equivalent to
(*f_ptr)();Following example fails to compile, as
predicate
is now ambiguous1
2
3
4
5
6bool predicate(const Foo&);
bool predicate(const Bar&);
void foo()
{
std::find_if(c.begin, c.end(), predicate);
}- Need to explicitly choose overload - example:
1
static_cast<bool(*)(const Foo&)>(predicate);
- Use generic lambda
- Compiles and works as intended
predicate
is an instance of a function object1
2
3
4
5const auto predicate = [](const auto&){ /* ... */ };
void foo()
{
std::find_if(c.begin(), c.end(), predicate);
}
- Need to explicitly choose overload - example:
Recap
- Closures produced by lambda expressions are first-class citizens
- They can be used like any other object in C++
- They can be used as first-class stateless functions or more flexible stateful function objects
Deprecated Standard Library Utilities Replaced by Lambdas
Many
<functional>
utilities were deprecated in C++11 and removed in C++17- Base
unary_function
binary_function
- Binders
binder1st
binder2nd
bind1st
bind2nd
- ** … **
...
- Base
The behavior of these deprecated functions can be achieved by using lambda expressions
1
2
3
4
5
6
7
8// Deprecated:
auto b0 = std::bind1st(f, 42);
// Recommended:
auto b0 = [](auto&& ... xs)
{
return f(42, std::forward<decltype(xs)>(xs) ... );
};
C++11 Standard Library Utilities Inferior to Lambdas
- The following utilities were either introduced in C++11:
std::mem_fn
std::bind
- For various reasons, they are inferior to lambdas and should be avoided
What’s New, And Proper Usage
- Stephan T. Lavavej, princple developer on the Visual C++ Libraries, gave an excellent talk at CppCon 2015: “
<functional>
: What’s New, And Proper Usage“ - In this talk, he explains the shortcomings of the aforementioned Standard Library functions
- https://www.youtube.com/watch?v=zt7ThwVfap0
mem_fn() Isn’t Fun, Really
- Good: Usually terse
- Bad: Resistant to optimization
- Ugly: Won’t compile in certain situations
- Overloaded member functions (need
static_cast
) - Templated member functions (use
static_cast
)- Avoid explicit template arguments. Don’t help the compiler
- Default arguments (no workaround)
- Overloaded member functions (need
- Unnecessary with anything powered by
invoke()
Recommendations
- Avoid using
mem_fn()
- Algorithm inner loops often affect performance
- As code evolves,
mem_fn()
is fragile - Auditing existing usage is low priority, though
- Use lambdas, especially generic lambdas
- They’re slightly more verbose
- But they optimize away
- And they always compile, like other member function calls
bind() Problems
- Same performance/compiler issues as
mem_fn()
- With function pointers in addition to PMFs/PMDs
- Misuse emits ultra-disgusting compiler errors
- Syntax isn’t normal C++, especially nested
bind()
- No short-circuiting for logical_and/logical_or
- Surprising behavior: bound args passed as lvalues
- Affects
unique_ptr
, etc.
- Affects
- Surprising behavior: immediate vs. delayed calls
- Placeholders and nested
bind()
can move twice
Recommendations
- Avoid using
bind()
- Use lambdas, especially generic lambdas
bind()
: good idea in 2005, bad idea in 2015- In C++, we usually prefer library solutions to Core
- But the library is terrible at building up function objects
- Lambdas were added to the Core Language for a reason
- STL maintainers rarely recommend avoiding the STL
bind()
‘s terseness just isn’t worth the price
std::mem_fn
Versus Lambda Expressions
- Given a member function pointer,
std::mem_fn
returns a call wrapper of unspecified type that can be invoked
1 | struct foo |
1 | auto foo_print = std::mem_fn(&foo::print); |
- Like
std::bind
and function pointers, it fails on overloads/templates
1 | struct foo |
1 | auto foo_print = std::mem_fn(&foo::print); |
ERROR: no matching function for call to
mem_fn(<unresolved overloaded function type>)
- Using a lambda here is a zero-cost abstraction that works with overloads/templates
1 | auto foo_print = [](auto&& this_foo) |
std::bind
Versus Lambda Expressions
- Given a function object
f
and some arguments,std::bind
returns a call wrapper of unspecified type equivalent to invokingf
with some of its arguments bound
1 | int add(int a, int b) { return a + b; } |
1 | // 1st arg is fixed to 10, 2nd arg is a placeholder that user can pass the arg to |
- Can work with overloads/templates
1 | int add(int a, int b) { return a + b; } |
1 | auto add10 = [](auto x) |
Summary
- Learned that closures produced by lambda expressions are
first-class
citizens - Understood that they can be assigned to variables, copied/moved, passed around, and so on
- studied that many
<functional>
utilities have been deprecated in C++11- lambda expressions can be used instead
- Understood that lambda expressions are superior to
std::mem_fn
andstd::bind
and should be used instead