Lambda HerbSutter
Lambda HerbSutter
Herb Sutter
Entrées
Effective STL: Choice of usability and performance
Algorithms à la Mode: Or any other way you like
Concurrency & Isolation: Active object sautéed with “mq” truffles
Parallelism & Scalability:
Loopy pasta and spun tasks, divided and conquered
Dessert
Initialization with const-sauce
Capture semantics
Syntax
int sum = 0;
long long product = 1;
for_each( values.begin(), values.end(), [&]( int i ) {
sum += i;
product *= i;
} );
Syntax
[ ] ( )opt -> opt { }
Examples
Earlier in scope: Widget w;
Examples
Earlier in scope: Widget w;
Lambdas == Functors
class __functor {
private:
CaptureTypes __captures;
public:
__functor( CaptureTypes captures )
: __captures( captures ) { }
Capture Example
class __functor {
private:
C1 __c1; C2& __c2;
public:
__functor( C1 c1, C2& c2 )
: __c1(c1), __c2(c2) { }
Parameter Example
class __functor {
public:
void operator()( P1 p1, const P2& p2 ) {
f( p1, p2 );
}
};
class __functor {
public:
template<typename T1, typename T2>
void operator()( T1 p1, const T2& p2 ) {
f( p1, p2 );
}
};
[] ( p1, p2 ) { f( p1, p2 ); }
class __functor {
public:
template<typename T1, typename T2>
void operator()( const T1& p1,
const T2& p2 ) {
f( p1, p2 );
}
};
Q: Now what?
Q: Now what?
A: Remember that there are two sides to auto…
Local/Nested Functions
As noted in GotW #58 (More Exceptional C++ Item 33), this
would be convenient for code hiding/locality, but isn’t legal:
int f( int i ) {
int j = i*2;
int g( int k ) { // error: can’t have a local function nested inside f
return j+k;
}
j += 4;
return g( 3 ); // attempt to call local function
}
Besides, what would be the semantics:
The original code probably expected g to use the value of j=i*2+4 at
the call point => capture by reference.
Sometimes we may want g to use the value of j=i*2 at the point g is
defined => capture by value.
Note: In C++03, you can’t instantiate a template with such a local class.
In C++0x, you can, so at least that drawback goes away.
} local;
local.j = i*2;
local.j += 4;
int f( int i ) {
int j = i*2;
auto g = [&] ( int k ) { // a local function nested inside f
return j+k;
};
j += 4;
return g( 3 ); // call local function
}
Entrées
Effective STL: Choice of usability and performance
Algorithms à la Mode: Or any other way you like
Concurrency & Isolation: Active object sautéed with “mq” truffles
Parallelism & Scalability:
Loopy pasta and spun tasks, divided and conquered
Dessert
Initialization with const-sauce
Capture semantics
The one thing that’s nicer is the C++0x range-based for loop:
for( auto& s : strings ) {
cout << s << endl;
}
But not by as much as before.
And the new for doesn’t compete directly with other STL algorithms.
Performance Footnote
Given:
char str[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
Performance Footnote
Given:
char str[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
Performance Footnote
Given:
char str[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
Entrées
Effective STL: Choice of usability and performance
Algorithms à la Mode: Or any other way you like
Concurrency & Isolation: Active object sautéed with “mq” truffles
Parallelism & Scalability:
Loopy pasta and spun tasks, divided and conquered
Dessert
Initialization with const-sauce
Capture semantics
But if you really like C#/Java keyword style, it’s just as easy:
lock( mutX, [&]{ synchronized( mutX, [&]{
… use x … … use x …
} ); } );
Entrées
Effective STL: Choice of usability and performance
Algorithms à la Mode: Or any other way you like
Concurrency & Isolation: Active object sautéed with “mq” truffles
Parallelism & Scalability:
Loopy pasta and spun tasks, divided and conquered
Dessert
Initialization with const-sauce
Capture semantics
class Backgrounder {
public:
void Save( string filename ) {
a.Send( bind( &Backgrounder::DoSave, this, filename ) );
}
void Print( Data& data ) {
a.Send( bind( &Backgrounder::DoPrint, this, ref(data) ) );
} // note: important ref()!
private:
… // thread-private data
Active a; // helper that encapsulates thread + msg queue/loop
void DoSave( string filename ) { … }
void DoPrint( Data& data ) { … }
};
class Backgrounder {
public:
void Save( string filename ) { a.Send( [=] {
…
} ); }
void Print( Data& data ) { a.Send( [=, &data] {
…
} ); }
private:
… // thread-private data
Active a; // helper that encapsulates thread + msg queue/loop
};
Not bad for not having active objects built into the language.
Entrées
Effective STL: Choice of usability and performance
Algorithms à la Mode: Or any other way you like
Concurrency & Isolation: Active object sautéed with “mq” truffles
Parallelism & Scalability:
Loopy pasta and spun tasks, divided and conquered
Dessert
Initialization with const-sauce
Capture semantics
A Sequential Loop
Perform Foo() on every element of an array:
Scalable Parallelization
Consider this sequential code:
void SendPackets( Buffers& bufs ) {
for( auto b : bufs ) {
Decorate( b );
Compress( b );
Encrypt( b );
}
}
Entrées
Effective STL: Choice of usability and performance
Algorithms à la Mode: Or any other way you like
Concurrency & Isolation: Active object sautéed with “mq” truffles
Parallelism & Scalability:
Loopy pasta and spun tasks, divided and conquered
Dessert
Initialization with const-sauce
Capture semantics
Initialization
Example question from “tohava”
(comp.lang.c++.moderated, March 2010):
Attempt #1: OK
Make the initialization a separate function:
int XInit( int N ) {
int ret = 1;
for( int i = 2; i <= N; ++i ) { // this could be some
ret += i; // arbitrarily long code
} // needed to initialize x
return ret;
}
Later on:
… elsewhere, elsewhen …
const int x = XInit( N ); // success
Advantage: It works.
Drawbacks:
(major) Loses locality.
(minor) Burns a function name.
Advantages:
(major) It works.
(major) Retains locality.
(minor) No new function name.
Entrées
Effective STL: Choice of usability and performance
Algorithms à la Mode: Or any other way you like
Concurrency & Isolation: Active object sautéed with “mq” truffles
Parallelism & Scalability:
Loopy pasta and spun tasks, divided and conquered
Dessert
Initialization with const-sauce
Capture semantics
Capture Default
Q: What should the "capture by" default be?
a) By reference?
b) By value?
Option A: By Reference
What if we implicitly took all captures by reference by default?
Counterexample:
Widget local = …;
auto fut = async( []{ DoWork( local ); } ); // A
DoOtherWork( local ); // B
return fut;
Option A: By Reference
What if we implicitly took all captures by reference by default?
Counterexample:
Widget local = …;
auto fut = async( []{ DoWork( local ); } ); // A
DoOtherWork( local ); // B
return fut;
Option B: By Value
What if we implicitly took all captures by value by default?
Counterexample:
vector<int> v = ReadBigHugeVectorFromDisk();
auto first = find( v.begin(), v.end(), 42 );
auto lambda = []{ FindNext( v, first ); };
Option B: By Value
What if we implicitly took all captures by value by default?
Counterexample:
vector<int> v = ReadBigHugeVectorFromDisk();
auto first = find( v.begin(), v.end(), 42 );
auto lambda = []{ FindNext( v, first ); };
Entrées
Effective STL: Choice of usability and performance
Algorithms à la Mode: Or any other way you like
Concurrency & Isolation: Active object sautéed with “mq” truffles
Parallelism & Scalability:
Loopy pasta and spun tasks, divided and conquered
Dessert
Initialization with const-sauce
Capture semantics
Common characteristics:
We want to treat a piece of code as an object, typically to pass.
The code is naturally local/one-off => should stay in local scope.