Everything you need to know about Lambdas, avoiding 'catch', and safe string-views in 75 mins or Less!
Erich Keane, iCDG Software Engineer
Erich.Keane@intel.com
-Lambdas
-Declarative Control Flow
-string_view
Lambda Functions
- Anonymous (unnamed) functions
- Invented in 1930s (Alonzo Church)
- First used in Lisp (1958), foundation of Functional Languages (Haskell, Erlang, etc)
- Since added to most imperative languages due to their usefulness
- Added to C++ as of C++11, given generics in C++14
- Incredibly useful for functions that don't NEED a name (only referred to 1x), and are used for a short-time
Lambdas in C++
- First Class Functions
- same efficiency as a Function Object
- In fact, implemented as Function Objects
- Full optimization
- Implicitly convertible to Function Pointers
- Useful for any function that takes a Predicate
- REALLY useful for function composition
- Usable with C function pointers
- Better replacement to std::bind (does partial application)
- ALWAYS compile-time typed
- Optimized aggressively
- Explicit copy/reference semantics
- Nicer Error Messages
- Can have state via statics
Lambda Syntax
4 Parts: []()->retType{}
- Capture []
- List of out-of-scope variables to gain access to
- Permits aliasing/state assignment in C++14
- Parameters List ()
- List of parameters (part of signature)
- Can use auto in C++14
- Optional, not required if there are no parameters!
- Return type not specified, it is deduced!
- Return Type: Almost never specified, type auto-deduces
- Block for Execution {}
- Any C++ compatible code that you'd want to use! Has access to Globals, local-statics, captures, and parameters
Lambda Syntax
Ways to Capture:
- Nothing: [](){}
- Everything as a copy: [=](){}
- Everything as a reference: [&](){}
- Single thing by copy: [varName](){}
- Single thing by reference:[&varName](){}
- Multiple things: [var1, &var2, var3](){}
- Everything but 1 by copy: [=,&refVar](){}
- Current object: [this](){}
- Rename (C++14): [v = varName](){}
- Init Value (C++14): [v = 12](){}
- Useful for calls to aggregate
Lambdas are Function Objects!
// Function object with state:
template<typename paramType>
class IsNotEqualFinder {
const paramType& orig;
public:
IsNotEqualFinder(const paramType& o):
orig(o){}
bool operator()(const paramType& pt){
return orig != pt;
}
};
Foo original;
std::vector<Foo> list = AllFoos();
IsNotEqualFinder<Foo> finder(original);
std::vector<Foo> listWoFoo;
std::copy_if(list.begin(), list.end(),
listWoFoo.begin(), finder); //C++14 Only!
Foo original;
std::vector<Foo> list = AllFoos();
auto finder = [&orig = original]
(auto pt){ return orig != pt;};
std::vector<Foo> listWoFoo;
std::copy_if(list.begin(), list.end(),
listWoFoo.begin(), finder);
//C++11:
Foo original;
std::vector<Foo> list = AllFoos();
auto finder = [&original]
(const Foo& pt)
{ return original != pt;};
std::vector<Foo> listWoFoo;
std::copy_if(list.begin(), list.end(),
listWoFoo.begin(), finder);Lambdas vs Functions
- Lambdas just as fast
- Terse sytnax!
- Use sparingly as a replacement, usually for Predicates/small functions that aren't reused elsewhere
- Lambdas can have state
- Otherwise identical capabilities
// Function mechanism, used only 1x,
// not necessarily nearby!
void DeleteThing(ThingType* thing){
delete thing;
}
// Calling site:
C_Call_With_Callback(myThing, &DeleteThing);
// Lambda Calling site:
C_Call_With_Callback(myThing, [](void* t){delete thing;});
// C++14 Lambda Calling Site:
C_Call_With_Callback(myThing, [](auto t){delete thing;});Lambdas vs Function Objects
struct CompareThings
{
CompareAndCount() = default;
bool operator()(const Thing& t1, const Thing& t2){
return t1 < t2;
}
};
std::vector<Thing> myThings = getThings();
CompareThings ctr;
std::sort(myThings.begin(), myThings.end(), ctr);
std::vector<Thing> myThings = getThings();
auto ctr = [](const auto& t1, const auto& t2){return t1 < t2;};
std::sort(myThings.begin(), myThings.end(), ctr);
Lambdas Instead of Bind
// Callback we want to use, note the context
struct OtherPart{
void CBHandler(void* context, char type, const Thing& stuff);};
// But call takes a function!
typedef void (*cb_func)(const Thing&/* stuff*/, char /*type*/, bool /*unused*/);
void StartAsyncThing(cb_func pFunc);
// Using Bind. harder to read, type erasure, everything captured by
// copy unless specified otherwise
StartAsyncThing(std::function<void(const Thing&, char, bool)>(
std::bind(&OtherPart::CBHandler, opPtr, addedContext, _2, _1)
)));
// Using Lambda:
StartAsyncThing(
[opPtr, addedContext](const Thing& stuff, char type, bool)
{ opPtr->CBHandler(addedContext, type, stuff); }
);
// Using Lambda, C++14!
StartAsyncThing(
[opPtr, addedContext](auto stuff, auto type, auto b)
{ opPtr->CBHandler(addedContext, type, stuff); }
);Declarative Control Flow
- try/catch blocks, and nested try/catch blocks get really bad
- try/catch blocks are tough to ensure they get properly unwound
- without a 'finally' (not in C++) try/catch blocks require repetition to cleanup on error
What can we do?
- Take advantage of RAII constructs to do cleanup for us!
- Create 3 types to handle the 3 different conditions for cleanup
- scope_exit // define what happens ALWAYS
- Not terribly necessary, since normal RAII containers can replace this
- alternatives: string/vector/smart-ptrs
- scope_success // define what happens on success
- scope_fail // define what happens when an exceptino is thrown
- Sadly, scope_success/scope_fail tougher in C++11/14
- Very easy in C++17 with int std::uncaught_exceptions instead of bool std::uncaught_exception
- scope_exit // define what happens ALWAYS
- Create 3 types to handle the 3 different conditions for cleanup
Example
// Instead of:
template<typename Fn, size_t N>
void write_file(const path& name,
Fn writeFunc, array<uint8_t, N> data)
{
create_file(name);
try
{
writeFunc(data);
save_and_close(name);
log("write file done");
}
catch(...)
{
delete_file(name);
log("write file done");
throw;
}
}// This!
template<typename Fn, size_t N>
void write_file(const path& name,
Fn writeFunc, array<uint8_t, N> data)
{
scope_exit exGuard([]
{log("write file done");});
create_file(name);
scope_fail failGuard([&name]
{delete_file(name);});
scope_success successGuard([&name]
{save_and_close(name);});
writeFunc(data);
}
Simple Implementation
class scope_exit{
private:
(void*)() func;
public:
scope_exit((void*)() f) : func(f){}
~scope_exit(){func();}
};class scope_fail{
private:
(void*)() func;
int count;
public:
scope_fail((void*)() f) : func(f), count(uncaught_exceptions()){}
~scope_fail(){
if (count != uncaught_exceptions()) func();
}
};
class scope_success{
private:
(void*)() func;
int count;
public:
scope_success((void*)() f) : func(f), count(uncaught_exceptions()){}
~scope_success(){
if (count == uncaught_exceptions()) func();
}
};
string_view
- C++17, named std::experimental::string_view
- Also boost::string_ref
- Describes an object that refers to a constant, contiguous sequence of char-like objects
- Provides a "view" into any type of string
- Non-owning, lifetime management up to user
- Almost the same interface as a constant std::string
- Implemented simply as a pointer and a length
- When to use?
- Passing as a parameter to a 'pure' function (one that doesn't modify the parameter)
- Returning a reference from a function
- A reference to a part of a long-lived structure
string_view legacy calls
// Original
void legacy_call(const char *param){...}
legacy_call("Thing being Frobbled");
string foo;
legacy_call(foo.c_str());
Partial conversion is less efficient:
void legacy_call(const string& param){...}
legacy_call("Thing being Frobbled");
string foo;
legacy_call(foo.c_str()); // results in an extra std::temporary
OR!:
void legacy_call(string_view param){...}
legacy_call("Thing being Frobbled");
string foo;
// identical, except size only calculated 1x
legacy_call(foo.c_str());
// No extra copy, size is now consistent in legacy call!
legacy_call(foo);
string_view construction
// std::string:
std::string other;
string_view view(other);
// Pointer + length:
void foo(char* ptr, int size)
{
string_view myView {{ptr, size}};
}
// Null terminated string (requires traversal)
char* other;
string_view view(other);string_view and string
// Compares to a std::string:
string_view a;
string b;
if (a == b){...}
// Compares to a null terminated string
if (a == "null term string"){...}
char* c = "null term string";
if (a == c){...}
// converts to a string:
string b = view.to_string();
// good 'find' methods:
find // find first character/substring
rfind // find last character/substring
find_first_of/find_last_of/find_first_not_of/find_last_not_of
// Additional good functions
operator[], at
front/back
size/length
empty
begin/end/cbegin/cend/rbegin/rend/crbegin/crend
data()
remove_prefix // shrink view by advancing ptr
remove_suffix // shrink view by shrinking length
substr // gets string_view to a smaller section
string_view drawbacks
- Not null terminated
- Lifetime externally managed
- Only C++17, though boost::string_ref does same things!
string_view usage example
#include <iostream>
using std::string;
using std::cout;
using std::endl;
string extractB(const string& o)
{
auto wobraces = o.substr(1, o.size() -2);
auto justB = wobraces.substr(
wobraces.find('b') - 1);
auto justVal = justB.substr(
justB.find(':') + 1);
auto noQuotes = justVal.substr(
1, justVal.size() - 2);
return noQuotes;
}
int main()
{
string orig = "{'a':'aval','b':'bval'}";
string after = extractB(orig);
cout << after <<endl;
}
string_view usage example
#include <iostream>
using std::string;
using std::cout;
using std::endl;
string extractB(const string& o)
{
auto wobraces = o.substr(1, o.size() -2);
auto justB = wobraces.substr(
wobraces.find('b') - 1);
auto justVal = justB.substr(
justB.find(':') + 1);
auto noQuotes = justVal.substr(
1, justVal.size() - 2);
return noQuotes;
}
int main()
{
string orig = "{'a':'aval','b':'bval'}";
string after = extractB(orig);
cout << after <<endl;
}
#include <iostream>
#include <experimental/string_view>
using std::string;
using std::cout;
using std::endl;
using std::experimental::string_view;
string extractB(string_view o)
{
auto wobraces = o.substr(1, o.size() -2);
auto justB = wobraces.substr(
wobraces.find('b') - 1);
auto justVal = justB.substr(
justB.find(':') + 1);
auto noQuotes = justVal.substr(
1, justVal.size() - 2);
return noQuotes;
}
int main()
{
string orig = "{'a':'aval','b':'bval'}";
string after = extractB(orig);
cout << after <<endl;
}
Q&A
Erich.Keane@intel.com
Everything you need to know about Lambdas, avoiding 'catch', and safer-
By Erich Keane
Everything you need to know about Lambdas, avoiding 'catch', and safer-
- 670