Skip to content

Instantly share code, notes, and snippets.

@matkatmusic matkatmusic/Part20.cpp Secret
Last active Jul 18, 2019

Embed
What would you like to do?
Lambdas
/*
https://www.ProgrammingForMusicians.com script
~lambdas~
hey folks, welcome back to the next episode of Programming for Musicians. In the last video we learned
about operator overloading, and ended with the ideas of how lambdas are built off of the call operator.
Let’s dig in to that:
Let's go to Coliru and make it clean
what is the call operator?
the call operator is a function you can add to your type to make an instance of that type callable:
[[ coliru ]]
Here’s the syntax:
*/
<return type> operator() ( <argument list> )
{
//body
}
/*
[[ new coliru tab ]]
How do you make something callable?
implement the call operator function for your type:
*/
#include <iostream>
struct Foo
{
void operator()()
{
std::cout << "you called foo()" << std::endl;
}
};
int main()
{
Foo foo;
foo();
}
/*
don’t be confused by the double set of parentheses ()(). the 'operator()' ["operator open parentheses close parentheses" is
the first part of the name.
the 2nd set of parentheses is the argument list, that all functions have.
By adding this, our Foo becomes callable. just like every other function, it can return a type and accept arguments.
for example:
*/
#include <iostream>
struct Foo
{
int operator()(int a, int b)
{
std::cout << "you called foo(" << a << ", " << b << ")" << std::endl;
return 42;
}
};
int main()
{
Foo foo;
//foo();
}
/*
Let’s add 2 more objects to our test(), that foo might be interested in:
*/
#include <iostream>
struct BigBar
{
float f{2.5f};
double d{3.14};
int result;
};
struct Foo
{
int operator()(int a, int b)
{
std::cout << "you called foo(" << a << ", " << b << ")" << std::endl;
return 42;
}
};
int main()
{
Foo foo;
BigBar bar;
//foo();
}
/*
And let’s say that we wanted Foo to know about BigBar, so we pass that instance of BigBar into our Foo constructor
and store it as a reference:
*/
#include <iostream>
struct BigBar
{
float f{2.5f};
double d{3.14};
int result;
};
struct Foo
{
Foo(BigBar& b) : bar(b) { }
int operator()(int a, int b)
{
std::cout << "you called foo(" << a << ", " << b << ")" << std::endl;
return 42;
}
BigBar& bar;
};
int main()
{
Foo foo;
BigBar bar;
//foo();
}
/*
now our main looks like this, because Foo requires a bar be passed to it:
*/
#include <iostream>
struct BigBar
{
float f{2.5f};
double d{3.14};
int result;
};
struct Foo
{
Foo(BigBar& b) : bar(b) { }
int operator()(int a, int b)
{
std::cout << "you called foo(" << a << ", " << b << ")" << std::endl;
return 42;
}
BigBar& bar;
};
int main()
{
BigBar bar;
Foo foo(bar);
//foo();
}
/*
we’ll update the call to foo() with arguments and store the result in bar’s member variable result:
*/
#include <iostream>
struct BigBar
{
float f{2.5f};
double d{3.14};
int result;
};
struct Foo
{
Foo(BigBar& b) : bar(b) { }
int operator()(int a, int b)
{
std::cout << "you called foo(" << a << ", " << b << ")" << std::endl;
return 42;
}
BigBar& bar;
};
int main()
{
BigBar bar;
Foo foo(bar);
bar.result = foo(42, 2);
}
/*
then we'll update foo’s operator() do something cool, now that it knows about bar:
*/
#include <iostream>
struct BigBar
{
float f{2.5f};
double d{3.14};
int result;
};
struct Foo
{
Foo(BigBar& b) : bar(b) { }
int operator()(int a, int b)
{
std::cout << "you called foo(" << a << ", " << b << ")" << std::endl;
return static_cast<int>( ( bar.f + bar.d * a) / static_cast<float>(b) );
}
BigBar& bar;
};
int main()
{
BigBar bar;
Foo foo(bar);
bar.result = foo(42, 2);
}
/*
Now, what if I told you there was a simpler way to write that?
[[ new coliru tab ]]
*/
int main()
{
BigBar bar;
bar.result = [&](int a, int b) -> int
{
std::cout << "you called foo(" << a << ", " << b << ")" << std::endl;
return static_cast<int>( (bar.f + bar.d * a) / static_cast<float>(b) );
}(42,2);
}
/*
how wild is that!!!
let’s look at what is happening:
first the [&]
This means that everything in the surrounding scope is captured by reference.
So, imagine an anonymous struct that lives inside the scope its used in that creates reference members for
everything in the surrounding scope like this:
[[ new coliru tab ]]
*/
struct BigBar
{
float f{2.5f};
double d{3.14};
int result;
};
struct Button { };
struct Vehicle {};
struct Car { };
int main()
{
Button b;
BigBar bar;
Vehicle v;
Car c;
struct Anonymous
{
Button& b;
BigBar& bar;
Vehicle& vehicle;
Car& c;
};
}
/*
then adds a constructor to initialize those reference members:
*/
int main()
{
Button b;
BigBar bar;
Vehicle v;
Car c;
struct Anonymous
{
Button& b;
BigBar& bar;
Vehicle& vehicle;
Car& c;
Anonymous(Button& _b, BigBar& _bar, Vehicle& _v, Car& _c) :
b(_b), bar(_bar), vehicle(_v), c(_c)
{ }
};
}
/*
next the (int a, int b) [[ show tab with lambda ]]
this becomes the argument list for the call operator that is added to the anonymous class:
*/
struct Anonymous
{
Button& b;
BigBar& bar;
Vehicle& vehicle;
Car& c;
Anonymous(Button& _b, BigBar& _bar, Vehicle& _v, Car& _c) :
b(_b), bar(_bar), vehicle(_v), c(_c)
{ }
operator() (int a, int b)
};
/*
then the -> int [[ show tab with lambda ]]
this is the return type for the function.
And it is optional as long as the compiler can deduce the return type.
if we don’t write
*/
-> <type>
/*
the compiler will figure it out for us based on the return statement within the curly braces, if there is one.
the syntax is an example of 'trailing return type'. we'll see more of this later.
*/
struct Anonymous
{
Button& b;
BigBar& bar;
Vehicle& vehicle;
Car& c;
Anonymous(Button& _b, BigBar& _bar, Vehicle& _v, Car& _c) :
b(_b), bar(_bar), vehicle(_v), c(_c)
{ }
int operator() (int a, int b)
};
/*
and obviously, the { } become the body of the call operator [[ show tab with lambda ]]
*/
struct Anonymous
{
Button& b;
BigBar& bar;
Vehicle& vehicle;
Car& c;
Anonymous(Button& _b, BigBar& _bar, Vehicle& _v, Car& _c) :
b(_b), bar(_bar), vehicle(_v), c(_c)
{ }
int operator() (int a, int b)
{
std::cout << "you called foo(" << a << ", " << b << ")" << std::endl;
return static_cast<int>( (bar.f + bar.d * a) / static_cast<float>(b) );
}
};
/*
last but not least, the (42,2); after the closing curly brace [[ show tab with lambda ]]
That's actually *calling* the call operator of our anonymous class.
so this:
*/
#include <iostream>
struct BigBar
{
float f{2.5f};
double d{3.14};
int result;
};
struct Button { };
struct Vehicle {};
struct Car { };
int main()
{
Button b;
BigBar bar;
Vehicle v;
Car c;
struct Anonymous
{
Button& b;
BigBar& bar;
Vehicle& vehicle;
Car& c;
Anonymous(Button& _b, BigBar& _bar, Vehicle& _v, Car& _c) :
b(_b), bar(_bar), vehicle(_v), c(_c)
{ }
int operator() (int a, int b)
{
std::cout << "you called foo(" << a << ", " << b << ")" << std::endl;
return static_cast<int>( (bar.f + bar.d * a) / static_cast<float>(b) );
}
};
Anonymous anon(b, bar, v, c);
bar.result = anon(42, 2);
}
/*
is identical to this:
[[ show tab with lambda ]]
*/
int main()
{
BigBar bar;
bar.result = [&](int a, int b) -> int
{
std::cout << "you called a lambda(" << a << ", " << b << ")" << std::endl;
return static_cast<int>( (bar.f + bar.d * a) / static_cast<float>(b) );
}(42, 2);
}
/*
Where’s my microphone? it needs to be dropped.
/////////////////////////// END PART 1
Lambdas are one of the best features of modern C++, and they have replaced function pointers.
They're an incredibly useful way of writing a function that will only be called from within a specific scope,
which helps keep your overall class interface clean and simple.
Instead of having a bunch of short private functions that clutter up your class's header file, you can just create and call
a lambda inside of the function where you need it executed.
There is a special object type in the standard library called std::function, and it wraps a callable object
it looks like this:
[[ new Coliru tab ]]
*/
std::function<void()> func;
/*
the 'void' is the return type of the wrapped callable, and the () is the argument list for that callable's call operator
function.
It is used in a lot of code nowadays as a function argument, where you’re supposed to supply a lambda.
when you create a std::function, it holds a nullptr and is not yet callable.
it must be assigned to a lambda or a function pointer before you can call it
What is a function pointer?
Function pointers are an ugly C syntax relic that we'll look at briefly.
Basically what you're doing when you write them is defining a type, where that type happens to be a Function.
now we know about 3 types: primitives, user defined types, and now Callables.
What are the 2 things that define a function?
the return type and the argument list.
So, a function like this:
*/
void foo(int bar) { }
/*
looks like this as a function pointer:
*/
void(*)(int);
/*
If it were to be expressed in a std::function<>, that std::function would look like this:
*/
std::function <void(int)>
/*
back to understanding function pointers.
// comment out above examples of func pointer syntax.
The tricky part is understanding the (*)
Sometimes this has a name, like (*funcPtr) and sometimes it doesn't, like void(*)(int);
If you come across it in some code and you see it with a name, it's a named variable because it's going to be used.
here's an example:
*/
int funcUsingFuncPointers( int(*funcPointer)(bool, int) )
{
return funcPointer(true, 3);
}
/*
notice that the name of the parameter to 'funcUsingFuncPointers' is called 'funcPointer', it returns an int, and it takes 2
arguments, a bool and an int.
The confusing bit is that the name of the parameter is in the MIDDLE of the Type of that parameter.
We've always seen the name AFTER the parameter type
and notice that it's being used inside the function.
here are 2 examples of that example in action, showing how it can be used:
*/
//return type and parameter list match the signature of the parameter of funcUsingFuncPointers
int optionalInvert(bool b, int a)
{
if(b) { return a; }
return -a;
}
//return type and parameter list match the signature of the parameter of funcUsingFuncPointers
int optionalDoubleInvert(bool b, int a)
{
if( b ) { return a*a; }
return -a*a;
}
int main()
{
int a = funcUsingFuncPointers( optionalInvert );
int b = funcUsingFuncPointers( optionalDoubleInvert );
/*
the thing that confuses most people (especially me) about function pointers is that you don't use them
via the address-of operator
Notice that i passed 'foo', and not '&foo' to 'funcUsingFuncPointers.
here's using that same function, but passing a lambda to it instead of a function.
*/
int c = funcUsingFuncPointers( [](bool b, int a) -> int
{
if( b) { return a*a*a;}
return b;
});
/*
This works because the lambda's operator() gets defined as:
*/
int operator(bool b, int a)
{
if( b) { return a*a*a;}
return b;
}
/*
the lambda's call operator has the same signature as those other functions
If you're not fully understanding where the lambda's call operator is coming from
rewatch the earlier part of this video, where that is explained.
Back to function pointers,
Function Pointers become much easier to use if you pair them with the 'using' keyword. as in:
*/
using MathFunc = int(*)(bool, int);
int funcUsingFuncPointers2(MathFunc func, bool b, int a)
{
return func(b, a);
}
int main()
{
int a = funcUsingFuncPointers( optionalInvert );
int b = funcUsingFuncPointers( optionalDoubleInvert );
int c = funcUsingFuncPointers( [](bool b, int a) -> int
{
if( b) { return a*a*a;}
return b;
});
int d = funcUsingFuncPointers2( optionalInvert, true, 3 );
}
/*
proof: http://coliru.stacked-crooked.com/a/7c54d5008046d6ef
That wraps up Function Pointers, which is the granddaddy to Lambdas and std::function
////////////////////////////// END PART 2
Let’s look at an example that could benefit from some lambda refactoring.
We’ll go back to the popup window example from the ~Pointers, References, the
New keyword conclusion~ video
[[ new coliru tab ]]
*/
struct Owner;
struct PopupWindow
{
Owner& owner;
PopupWindow(Owner& o) : owner(o) { }
void closeWindow();
};
struct Owner
{
PopupWindow popupWindow;
Owner() : popupWindow(*this) { }
void closePopupWindow( PopupWindow* windowToClose ) { std::cout << "closing popup window" << std::endl; }
};
void PopupWindow::closeWindow()
{
owner.closePopupWindow( this );
}
int main()
{
return 0;
}
/*
if you recall, that design resulted in tight coupling of the owner and the popup window.
with the use of a lambda, we can disconnect the two, and also not have to use that Listener/callback
pattern that was shown with the Button, Button::Listener example from that same video
consider this change:
[[ new coliru tab ]]
*/
#include <functional>
struct PopupWindow
{
void closeWindow()
{
if( onClose )
onClose();
}
std::function<void()> onClose;
};
struct Owner
{
PopupWindow popupWindow;
void closePopupWindow( PopupWindow* windowToClose ) { std::cout << "closing popup window" << std::endl; }
};
int main()
{
return 0;
}
/*
It looks really simple, right? The initialization of each class is a lot simpler, that's for sure!
Instead of calling a member function on the owner instance, if a callable has
been assigned to our onClose member, call that callable!
Let’s set up our Owner class to make use of it:
*/
#include <iostream>
#include <functional>
struct PopupWindow
{
void closeWindow()
{
if( onClose )
onClose();
}
std::function<void()> onClose;
};
struct Owner
{
void close() { popupWindow.closeWindow(); } // <- add this
Owner()
{
popupWindow.onClose = [this]() // <- and this
{
this->closePopupWindow( &popupWindow );
};
}
void closePopupWindow( PopupWindow* windowToClose ) { std::cout << "closing the window" << std::endl; }
private: // <- and this
PopupWindow popupWindow;
};
int main()
{
Owner owner; // <- and this
owner.close(); // and this
return 0;
}
/*
Lambdas FTW
//////////////////////////////// END PART
let's talk about the lambda capture rules.
[[ show URL ]]
The cppreference page for lambdas, specifically the Lambda Capture rules
(https://en.cppreference.com/w/cpp/language/lambda#Lambda_capture) explains the ways to capture
surrounding scope variables, but as an overview:
the [this] captures the pointer to whatever object we are currently inside, as a reference:
[[ new coliru tab ]]
*/
http://coliru.stacked-crooked.com/a/7edf529f4c14edc4
/*
every usage of 'this->' inside the lambdas’s body for the above would be replaced with ‘foo.’.
you don’t /have/ to write 'this->' inside the call operator’s body, the compiler will know what object you’re
referring to when you use the member variables of ‘this’.
[[ new coliru tab ]]
[&] captures everything in the surrounding scope by reference:
*/
http://coliru.stacked-crooked.com/a/b51014f1bbca9815
/*
[[ new coliru tab ]]
[=] captures everything by copying it:
*/
http://coliru.stacked-crooked.com/a/a7e6b9d71e443356
/*
note the use of the 'mutable' keyword. check out what happens if we don't include it.
[[modify example]]
[[ new coliru tab ]]
when you name things explicitly, they’re captured by copy, unless you put the & in front of their name,
then they’re captured by reference.
*/
//http://coliru.stacked-crooked.com/a/1426e62a8b9a378f
/*
It's good practice to only capture what you actually need to use in your lambda body.
It's too easy to make mistakes doing broad captures, and tends to capture way more than you need.
Be sure to note the difference between capturing by copy vs. by reference when you're capturing something other than a
primitive.
copying by value might result in an allocation.
but capturing by reference might result in a dangling reference if the lifetime of the captured object ends before the
lambda body is invoked.
[[ show tab with BigBar/vehicle/car ]]
I’m going to recommend that you check out the cppreference page for Lambdas to learn all of the details.
//////////////////////////////// END PART
Let’s go back to our popupwindow, clone it to a new tab, and modify it so that it takes a lambda in its constructor and initialize our
lambda to a default value if no lambda is provided in the constructor:
[[ new coliru tab ]]
*/
#include <iostream>
#include <functional>
struct PopupWindow
{
PopupWindow( std::function<void()> func = []() { } ) : onClose( std::move(func) )
{ }
void closeWindow()
{
if( onClose )
onClose();
}
std::function<void()> onClose ;
};
struct Owner
{
void close() { popupWindow.closeWindow(); }
Owner()
{
popupWindow.onClose = [this]()
{
this->closePopupWindow( &popupWindow );
};
}
void closePopupWindow( PopupWindow* windowToClose ) { std::cout << "closing the window" << std::endl; }
private:
PopupWindow popupWindow;
};
int main()
{
Owner owner; // <- and this
owner.close(); // and this
return 0;
}
/*
our default lambda captures nothing, takes no arguments to its call operator function, and has no statements in the
function body.
that std::move() is a special function, that is pretty complicated, so don’t even worry about it for now.
we'll discuss it in the video about Move Semantics.
Moving on..
if our default lambda were a user-defined type it would look like this:
*/
struct Anon
{
Anon() { }
void operator()() { }
};
/*
Let’s modify our Owner class so that the lambda gets passed to the constructor of our PopupWindow when we
initialize it:
*/
struct Owner
{
void close() { popupWindow.closeWindow(); }
Owner(): popupWindow( [this]() { this->closePopupWindow( &popupWindow ); } ) { /* ... */ }
void closePopupWindow( PopupWindow* windowToClose ) { std::cout << "closing the window" << std::endl; }
private:
PopupWindow popupWindow;
};
/*
Look how clean and concise that is! And our PopupWindow’s action is completely decoupled from the Owner class.
And actually, can we initialize our popup window in class?
yes we can!
*/
struct Owner
{
void close() { popupWindow.closeWindow(); }
void closePopupWindow( PopupWindow* windowToClose ) { std::cout << "closing the window" << std::endl; }
private:
PopupWindow popupWindow { [this]() { this->closePopupWindow( &popupWindow ); } };
};
/*
Awesome. that made our owner even simpler, removing the constructor completely!
Awesome sauce!
one last thing, lambdas have weird types.
most people write this, since the compiler knows what type they actually are:
[[ new CPPInsights tab ]]
*/
#include <iostream>
int main()
{
auto lambda = []() { std::cout << "lambda body" << std::endl; };
lambda();
}
//
/*
‘auto’ instructs the compiler “figure out this type for me, i’m initializing it with the expression on the right”
let's run it to see what kind of name the compiler comes up with for that lambda
yeesh. wild stuff. To answer your question of "what is that stuff after the call operator":
lambdas that don't capture anything can be cast to function pointers with the same function signature.
you'll notice the `using` keyword here.
and below it is a conversion function that returns the function pointer.
What is it actually returning though?
It's returning a static member function that has an identical body to the call operator
static member functions behave exactly like free functions, except that they need to be preceded with
ClassName::
But other than that, static member functions are basically free functions.
and when you see some function that wants a function pointer passed to it as a parameter, it's asking for a free function.
//////////////////// END PART
I mentioned it earlier, but don't forget that a lambda's lifetime is tied to where it's created.
When you capture 'this' in a lambda, you need to be careful that the lambda's use of 'this' doesn't
outlive the lifetime of 'this'.
It is a common source of debugging hell when you start using lambdas across threads.
This shows up when you try to trigger GUI events from the audio thread, for example.
in Juce's timer class, there is a static function 'callAfterDelay' which executes the lambda body after a period of time.
consider the following snippet : //
[[ xcode ]]
*/
struct Foo
{
Foo()
{
Timer::callAfterDelay(1000, [this]()
{
this->someFunction();
});
}
private:
void someFunction() { DBG( data << " whoops!!" ); }
int data{42};
};
//==============================================================================
MainComponent::MainComponent()
{
Foo foo;
setSize (600, 400);
}
/*
As you can see, the lambda's lifetime has outlasted the object it captured, which means 'this' is a dangling pointer.
be careful with the lifetimes of what you capture in the lambdas.
make sure its safe to use those objects before using them.
that can be difficult if you're capturing by reference.
the final thing I want to say about lambdas that if you capture by copy, whatever you capture is const
"uh, can you show an example of what you mean?"
yes I can.
[[ new coliru tab ]]
http://coliru.stacked-crooked.com/a/c6d3bbc15d9abe93
[[explain snippet]]
[[run snippet]]
You'll notice that we have this mutable keyword being used. This gets us around this const restriction
the reason for this is because the call operator in the lambda is marked const according to the C++ standard:
http://eel.is/c%2B%2Bdraft/expr.prim.lambda.closure#4
you have to use the 'mutable' keyword to make those objects captured by copy non-const.
if you capture an object by copy that has member functions, you can only call the const member functions inside the
lambda
However, if you specify the lambda is mutable, you can then use the non-const member functions of that object inside the lambda.
Why doesn't this same thing apply if you capture by reference?
If you capture something by reference, when you modify the referenced object, you're not modifying a member of `this`.
you're modifying an outside object. that object's `const-ness` is not affected by the const-ness
of that call operator
see:
https://cppinsights.io/lnk?code=c3RydWN0IEZvbyB7IGludCBmOyB9OwpzdHJ1Y3QgQmFyCnsgCglGb28mIGZvbzsgCglCYXIoIEZvbyYgZikgOiBmb28oZikgeyB9Cgl2b2lkIG9wZXJhdG9yKCkoKSBjb25zdAoJewoJCWZvby5mID0gMzsKCX0KfTsKCmludCBtYWluKCkKewoJRm9vIGZvbzsKICAJQmFyIGJhcihmb28pOwoJYmFyKCk7Cn0=&insightsOptions=cpp17&std=cpp17&rev=1.0
///////////////////////////////////////////
Let’s recap:
[[ show the appropriate tab for each recap ]]
lambda syntax involves first the capture list, expressed inside [ ]
then the argument list for the operator() function,
then the optional return type, expressed as arrow type( -> int ) [trailing return type]
then the function body;
[[ button, bigbar, vehicle tab ]]
Lambdas behave like anonymous structures with a call operator
[[ popup window tab #2 ]]
Lambdas make code a lot cleaner and allow decoupling of classes, that would otherwise be tightly coupled or
rely on inheritance/listener/callback patterns, which can result in a lot of boring-to-write code
[[ cpp insights tab ]]
Lambdas are often stored in local function variables via the ‘auto’ keyword, as trying to name a Lambda's type is
basically impossible. it's best left to the compiler to come up with a name.
[[ function pointer tab ]]
function pointers are ugly, and the predecessor to lambdas. you can pass lambdas to functions that have
function pointers as one of their arguments.
the best way to declare a function pointer is via the 'using' keyword:
*/
using FuncPointer = <return type>(*)(<argument list>);
/*
but if you see them used as arguments to a function, don't be scared of the syntax:
*/
void foo( <return type>(*<optional func name>)(<argument list>) );
/*
[[ xcode ]]
lambda usage requires careful consideration of whatever they are capturing by reference, as the lambda might
be executed when the object that owns the scope it was created in no longer exists.
One last comment. lambdas aren't free, in terms of memory.
Whatever you capture takes up memory.
so if you decide to pass around a lot of std::function objects, if your lambda captures a ton of stuff
(by capturing =), your std::function could cause a slow down when its copied.
[[ Data example tab ]]
anything the lambda captures by copy is const and can't be modified, unless you mark the lambda as 'mutable'
Alright, awesome. we have one of the most powerful tools at our disposal now.
In the next video, we will learn another super-powerful tool: Templates, and an example of them that we’ve already seen:
Containers aka Arrays, like std::vector<>
As an idea for how all of this might get used:
imagine you have a plugin that has a combobox or dropdown menu for picking which waveform to use to generate sound.
when you make a choice, imagine that you have a vector full of std::functions, and each std::function
has a signature of float(float), and the body of the lambda you give it IS your waveform generator function.
so, sine wave, saw wave, square wave, etc... all expressed as lambdas, that are stored in a vector, and get used
when you pick which waveform to use from your combobox!
ch5_p03 task: https://coliru.stacked-crooked.com/a/e5797bfaa8e32ab1
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.