0% found this document useful (0 votes)
80 views

Vittorio Romeo - Higher Order Functions and Function Ref

The document discusses higher-order functions and function references. It begins with an introduction and definitions of higher-order functions. It then provides examples of higher-order functions in C++ and discusses how they are implemented using templates and closures. The document also examines existing higher-order functions in the C++ standard library.

Uploaded by

jaansegus
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
80 views

Vittorio Romeo - Higher Order Functions and Function Ref

The document discusses higher-order functions and function references. It begins with an introduction and definitions of higher-order functions. It then provides examples of higher-order functions in C++ and discusses how they are implemented using templates and closures. The document also examines existing higher-order functions in the C++ standard library.

Uploaded by

jaansegus
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 85

Higher-order functions and

function_ref

Vittorio Romeo C++ On Sea 2019


vittorioromeo.info 2019/02/05
[email protected] Folkestone, UK
[email protected]
@supahvee1234
About me
Software Engineer @ Bloomberg L.P.
Introduction
introduction 9
what is this talk about?

Higher-order functions
What they are
Use cases and implementation techniques
function_ref
Motivation
Specification and usage examples
Implementation
Benchmarks

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
introduction 10
disclaimer

This is not a talk on functional programming


We are going to look at:
Pratical every day uses of higher-order functions
Existing "functional" facilities in the language
Design and implementation of a ISO C++20 proposal

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
introduction 11
assumptions

You are somewhat familiar with:


Lambda expressions
Templates
Modern C++ features
Do not hesitate to ask questions

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
12

Higher order functions

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 13
definition

In mathematics and computer science, a higher-order function is a function that


does at least one of the following:
takes one or more functions as arguments (i.e. procedural parameters),
returns a function as its result.
- Wikipedia

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 14
basic examples

template <typename F>


void call_twice(F f)
{
f();
f();
}
call_twice([]{ std cout "hello"; });

Takes a FunctionObject as an argument

Implementation technique: template parameter

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 15
basic examples

auto greater_than(int threshold)


{
return [threshold](int x)
{
return x > threshold;
};
}
std vector<int> v{0, 4, 1, 11, 5, 9};
assert(std count_if(v.begin(), v.end(), greater_than(5)) 2);
(on wandbox.org)

Returns a FunctionObject invocable with an int

Implementation technique: closure + auto return type

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 16
in the Standard

Do we have any higher-order function in the C++ Standard?

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 17
in the Standard

Do we have any higher-order function in the C Standard?

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 18
in the Standard

std qsort , std bsearch


std atexit , std at_quick_exit
std signal

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 19
in the Standard

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 20
in the Standard

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 21
in the Standard

#include <csignal>
int main()
{
std signal(SIGINT, [](int signal_num)
{
std cout "signal: " signal_num '\n';
});
}
(on godbolt.org)

Lambda expressions work great with higher-order functions


Stateless closures are implicitly convertible to function pointers

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 22
in the Standard

std set_terminate
std visit , std apply , std invoke
std bind , std bind_front (C++20)
<numeric> and <algorithm>
...

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 23
in the Standard

std vector<entity> entities;


entities.erase(
std remove_if(entities.begin(),
entities.end(),
[](const entity& e){ return !e._active; }),
entities.end()
);
(on godbolt.org)

"Erase-remove idiom":
Moves kept elements to the beginning of the range
Relative order of elements is preserved

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 24
in the Standard

using event = std variant<connect, disconnect, heartbeat>;


void process(event e)
{
std visit(
overload([](connect) { std cout "process connect\n"; },
[](disconnect){ std cout "process disconnect\n"; },
[](heartbeat) { std cout "process heartbeat\n"; }),
e);
}
process(event{connect{}});
process(event{heartbeat{}});
process(event{disconnect{}});
(on wandbox.org)

"Implementing variant visitation using lambdas" @ ACCU 2017, C++Now 2017

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 25
use cases

Avoiding repetition
Inversion of control flow
Asynchronicity
Compile-time metaprogramming
...

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 26
use cases - avoiding repetition

Code repetition leads to bugs and maintenance overhead


Sometimes, it is trivial to avoid

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 27
use cases - avoiding repetition

void test_routing(context& ctx)


{
const auto machine0 = ctx.reserve_port("127.0.0.1");
const auto machine1 = ctx.reserve_port("127.0.0.1");
const auto machine2 = ctx.reserve_port("127.0.0.1");
}

void test_routing(context& ctx)


{
const auto free_port = [&]{ return ctx.reserve_port("127.0.0.1"); };
const auto machine0 = free_port();
const auto machine1 = free_port();
const auto machine2 = free_port();
}
vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 28
use cases - avoiding repetition

Other times it can be more complicated

void widget update()


{
for (auto& c : this _children)
if (c visible())
c recalculate_focus();
for (auto& c : this _children)
if (c visible())
c recalculate_bounds();
for (auto& c : this _children)
if (c visible())
c update();
}

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 29
use cases - avoiding repetition

void widget update()


{
const auto for_visible_children = [this](auto f)
{
for (auto& c : this _children)
if(c visible())
f( c);
};
for_visible_children([](auto& c){ c.recalculate_focus(); });
for_visible_children([](auto& c){ c.recalculate_bounds(); });
for_visible_children([](auto& c){ c.update(); });
}

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 30
use cases - inversion of control flow

Pass an action/predicate to a function which deals with the control flow


Separate what happens from how it happens
Example: C++17 parallel algorithms

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 31
use cases - inversion of control flow

struct physics_component
{
vec2f _pos, _vel, _acc;
};
std vector<physics_component> components{ };
std for_each(std execution par_unseq,
components.begin(),
components.end(),
[](auto& c)
{
c._vel += c._acc;
c._pos += c._vel;
});

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 32
use cases - inversion of control flow

Decoupling control flow from the desired action


Can be reused & tested separately
Example: printing a comma-separated list of elements

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 33
use cases - inversion of control flow

Initial version

template <typename T>


void print(const std vector<T>& v)
{
if(std empty(v)) { return; }
std cout v.begin();
for(auto it = std next(v.begin()); it v.end(); it)
{
std cout ", ";
std cout it;
}
}
(on wandbox.org)

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 34
use cases - inversion of control flow

Identify the structure

template <typename T>


void print(const std vector<T>& v)
{
if(std empty(v)) { return; }
action
for(auto it = std next(v.begin()); it v.end(); it)
{
separation
action
}
}

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 35
use cases - inversion of control flow

Create an abstraction

template <typename Range, typename F, typename FSep>


void for_separated(Range range, F f, FSep f_sep)
{
if(std empty(range)) { return; }
f( range.begin());
for(auto it = std next(range.begin()); it range.end(); it)
{
f_sep();
f( it);
}
}

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 36
use cases - inversion of control flow

Redefine print

template <typename T>


void print(const std vector<T>& v)
{
for_separated(v,
[](const auto& x){ std cout x; },
[]{ std cout ", "; });
}
(on wandbox.org)

for_separated is reusable
It provides the control flow
The user provides the actions

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 37
use cases - inversion of control flow

const auto corrupt_print = [](const auto& sentence)


{
for_separated(sentence,
[](const auto& x){ std cout x; },
[]{ std cout rnd_char(); });
};
corrupt_print("helloworld"s);
(on wandbox.org)

h%e$ltl3o w/o�r l_d


h�eyl l�oPwCo r�l�d

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 38
use cases - inversion of control flow

template <typename Range, typename Pred, typename F>


void consume_if(Range range, Pred pred, F f)
{
for(auto it = std begin(range); it std end(range);)
{
if(pred( it))
{
f( it);
it = range.erase(it);
}
else { it; }
}
}
consume_if(_systems,
[](auto& system){ return system.initialize(); },
state_changer(State ReadyToSync));

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 39
use cases - asynchronicity

Currently the easiest way to express asynchronous callbacks


std future , std thread , ...
Many use cases might be superseded by coroutines

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 40
use cases - asynchronicity

auto graph = all


{
[]{ return http_get_request("animals.com/cat/0.png"); },
[]{ return http_get_request("animals.com/dog/0.png"); }
}
.then([](std tuple<data, data> payload)
{
std apply(stitch, payload);
});

"Zero-allocation & no type erasure futures" @ ACCU 2018, C++Now 2018

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 41
use cases - metaprogramming fun!

"zero-overhead C++17 currying & partial application"


"compile-time iteration with C++20 lambdas"

enumerate_types<int, float, char>([]<typename T, auto I>()


{
std cout I ": " typeid(T).name() '\n';
});

0: i
1: f
2: c

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 42
vs other abstractions

Sometimes other abstractions can be used to achieve the same goals


RAII guards
Iterators
...

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 43
vs RAII guards

Example: thread-safe access to an object via synchronized<T>

class foo { };
synchronized<foo> s_foo;
some way to access contents of `s_foo` in a thread safe manner

What interface should synchronized expose?

i. RAII guards
ii. Higher-order functions

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 44
vs RAII guards

synchronized<foo> s_foo; synchronized<foo> s_foo;


{ s_foo.access([](foo& f)
auto f = s_foo.access(); {
f some_foo_method(); f.some_foo_method();
} });

(+) Friendly to control flow (+) Simpler implementation

(−) Might require captures

(−) Unfriendly to control flow

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 45
vs RAII guards

template <typename T>


class synchronized
{
T _obj;
std mutex _mtx;

public:
auto access()
{
struct access_guard
{
std lock_guard<std mutex> _guard;
T* operator ();
constructors, etc
};

return access_guard{*this};
}
};

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 46
vs RAII guards

template <typename T>


class synchronized
{
T _obj;
std mutex _mtx;
public:
template <typename F>
auto access(F f)
{
std lock_guard guard{_mtx};
return std forward<F>(f)(_obj);
}
};
(on wandbox.org)

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 47
vs RAII guards

template <typename T>


class synchronized
{
T _obj;
std mutex _mtx;
public:
template <typename F>
auto access(F f)
{
return std lock_guard{_mtx}, std forward<F>(f)(_obj);
}
};
(on wandbox.org)

Way simpler to implement and review

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 48
vs RAII guards

Example: benchmarking a function

template <typename F>


auto benchmark(F f)
{
const auto t = std chrono high_resolution_clock now();
f();
return std chrono high_resolution_clock now() - t;
}

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 49
vs iterators

Example: iterating over filtered range

std vector<int> ints{ }; std vector<int> ints{ };


for(int x : filtered(ints, even)) for_filtered(ints, is_even,
{ [](int x){ });
} (+) Simpler implementation

(+) Friendly to control flow (−) Might require captures

(+) More composable with std (−) Unfriendly to control flow

(−) Complicated implementation

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 50
vs iterators

From Boost.Iterator

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 51
vs iterators

for_filtered(ints, is_even, [](int x){ });

template <typename Range, typename Pred, typename F>


void for_filtered(Range range, Pred pred, F f)
{
for(auto x : range)
if(pred(x))
f(x);
}

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
higher order functions 52
recap

Very powerful: many different use cases


Easier to write than existing alternatives
When you need a quick testable/reusable abstraction that doesn't have to be
composable, they're great
Language alternatives might come: coroutines, ranges, ...
Do not play nicely with control flow on the caller side
Consider return / break / continue in a lambda body

Even more powerful in C++17 and C++20


Some proposals might have helped... - e.g. P0573

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
53

function_ref

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 54
motivation

What options do we have to implement higher-order functions?

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 55
motivation

Pointers to functions
int operation(int( f)(int, int))
{
return f(1, 2);
}

Works with non-member functions and stateless closures


Doesn't work with stateful Callable objects

Small run-time overhead (easily inlined in the same TU)


Constrained, with obvious signature

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 56
motivation

Template parameters
template <typename T>
auto operation(F f) decltype(std forward<F>(f)(1, 2))
{
return std forward<F>(f)(1, 2);
}

Works with any FunctionObject or Callable , using std invoke


Zero-cost abstraction
Hard to constrain (less true in C++20)
Might degrade compilation time

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 57
motivation

std function
int operation(const std function<int(int, int)>& f)
{
return f(1, 2);
}

Works with any FunctionObject or Callable

Significant run-time overhead (hard to inline/optimize)


Constrained, with obvious signature
Unclear semantics: can be both owning or non-owning

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 58
motivation

function_ref
int operation(function_ref<int(int, int)> f)
{
return f(1, 2);
}

Works with any FunctionObject or Callable

Small run-time overhead (easily inlined in the same TU)


Constrained, with obvious signature
Clear non-owning semantics
Lightweight - think of " string_view for Callable objects"

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 59
in a nutshell

function_ref<R(Args )> is a non-owning reference to a Callable


Parallel:
std string → std string_view
std function → std function_ref
Doesn't own or extend the lifetime of the referenced Callable

Lightweight, noexcept and optimization-friendly

Proposed by me in P0792 - currently in LWG


Many thanks to: Agustín Bergé, Dietmar Kühl, Eric Niebler, Tim van Deurzen,
and Alisdair Meredith

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 60
practical comparsions

Why use function_ref instead of std function ?


Performance
"Clear" reference semantics
Why use function_ref instead of template parameters?

Easier to write/read/teach
Usable in polymorphic hierarchies
Better compilation times

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 61
synopsis

template <typename Signature>


class function_ref
{
void* object; exposition only
R( erased_function)(Args ) qualifiers; exposition only
public:
constexpr function_ref(const function_ref&) noexcept = default;

template <typename F>


constexpr function_ref(F );

constexpr function_ref& operator=(const function_ref&) noexcept = default;

template <typename F>


constexpr function_ref& operator=(F );

constexpr void swap(function_ref&) noexcept;

R operator()(Args ) const noexcept qualifier;


};
vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 62
use case examples

class replay_map
{
std unordered_map<command_id, ref_counted<command _items;
std unordered_map<queue_id, std deque<command_id _queues;
void iterate(const queue_id& id,
const function_ref<void(const Value&)> f) const
{
const auto queueIt = _queues.find(qk);
if(queueIt std end(_queues)) { return; }
const auto& q = queueIt second;
for(auto it = q.rbegin(); it q.rend(); it)
{
f(d_items.at( it).d_value);
}
}
};
vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 63
use case examples

struct packet_cache
{
using replay_cb = function_ref<void(const VALUE&)>;
using consume_cb = function_ref<void(VALUE )>;
virtual void replay(replay_cb cb) const = 0;
virtual void consume(consume_cb cb) = 0;
virtual ~packet_cache() { }
};

...

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 64
use case examples

struct contiguous_packet_cache : packet_cache


{

void replay(replay_cb cb) const override


{
for (const auto& p : _packets)
cb(p);
}
void consume(consume_cb cb) override
{
for (auto& p : _packets)
cb(std move(p));
clear();
}
};
vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 65
use case examples

using status_change_cb =
function_ref<void(const node_id&, const node_state_transition&)>;
void node_monitor sweep(const status_change_cb& cb,
const timestamp& ts)
{
for(auto it = std begin(_data); it std end(_data);)
{
if ( (it second._state node_state down)
(ts - v._last_heartbeat 10s))
{
cb(it first, transition_to(v, node_state Down));
it = _data.erase(it);
}
else { it; }
}
}

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 66
implementation

"Match" a signature though template specialization:

template <typename Signature>


class function_ref;
template <typename Return, typename Args>
class function_ref<Return(Args )>
{
};

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 67
implementation

Store pointer to Callable object and pointer to erased function:

template <typename Return, typename Args>


class function_ref<Return(Args )>
{
private:
void* _ptr;
Return (*_erased_fn)(void*, Args );
public:
};

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 68
implementation

private:
void* _ptr;
Return (*_erased_fn)(void*, Args );

On construction, set the pointers:

template <typename F>


function_ref(F f) noexcept : _ptr{(void*) &f}
{
_erased_fn = [](void* ptr, Args xs) Return
{
return (*reinterpret_cast<F (ptr))(
std forward<Args>(xs) );
};
}

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 69
implementation

private:
void* _ptr;
Return (*_erased_fn)(void*, Args );

On invocation, go through _erased_fn :

Return operator()(Args xs) const


{
return _erased_fn(_ptr, std forward<Args>(xs) );
}

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 70
implementation

template <typename Return, typename Args>


class function_ref<Return(Args )>
{
void* _ptr;
Return (*_erased_fn)(void*, Args );

public:
template <typename F, some constraints >
function_ref(F f) noexcept : _ptr{(void*) &f}
{
_erased_fn = [](void* ptr, Args xs) Return {
return (*reinterpret_cast<F (ptr))(
std forward<Args>(xs) );
};
}

Return operator()(Args xs) const noexcept( )


{
return _erased_fn(_ptr, std forward<Args>(xs) );
}
};
vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 71
pitfalls

What happens here?

const function_ref<int()> get_number = []{ return 42; };


std cout get_number() '\n';

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 72
pitfalls

How about here?

int get_number() { return 42; }


const function_ref<int()> f = &get_number;
std cout f() '\n';

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 73
benchmarks - methodology

Used quick-bench.com with Simon Brand's function_ref implementation

Internally uses Google Benchmark


Scenario: invoke simple higher-order function in a loop
Test with:
template parameter
function_ref
std function
Also with and without inlining

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 74
benchmarks - methodology

template <typename F>


void templateParameter(F f)
{
benchmark DoNotOptimize(f());
}
void stdFunction(const std function<int()>& f)
{
benchmark DoNotOptimize(f());
}
void functionRef(const tl function_ref<int()>& f)
{
benchmark DoNotOptimize(f());
}

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 75
benchmarks - methodology

template <typename F>


void attribute ((noinline)) noInlineTemplateParameter(F f)
{
benchmark DoNotOptimize(f());
}

void attribute ((noinline)) noInlineStdFunction(const std function<int()>& f)


{
benchmark DoNotOptimize(f());
}

void attribute ((noinline)) noInlineFunctionRef(const tl function_ref<int()>& f)


{
benchmark DoNotOptimize(f());
}

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 76
benchmarks - methodology

static void TemplateParameter(benchmark State& state) {


for (auto _ : state) {
templateParameter([]{ return 1; });
}
}
BENCHMARK(TemplateParameter);
static void FunctionRef(benchmark State& state) {
for (auto _ : state) {
functionRef([]{ return 1; });
}
}
BENCHMARK(FunctionRef);
and so on

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 77
benchmarks - results (0/2)

g 8.x , -O3 , libstdc - (link)

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 78
benchmarks - results (1/2)

clang 7.x , -O3 , libstdc - (link)

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 79
benchmarks - results (2/2)

clang 7.x , -O3 , libc - (link)

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
function_ref 80
benchmarks - conclusions

When inlining happens:


function_ref is as fast as a template parameter
std function is at least 7x slower than function_ref
When inlining doesn't happen:
function_ref is around 2x slower than a template parameter
std function is around 1.5x slower than function_ref
function_ref is optimizer-friendly and thrives with inlining
function_ref is always faster than std function

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
conclusion 81
recap - higher order functions

Any function accepting or returning another is an "higher-order function"


Many examples in both the C and C++ Standards
Varied use cases: avoiding repetition, inverting control flow, ...
Highly usable thanks to lambda expressions
Easier to implement compared to some alternatives
At the cost of introducing an extra function scope
You don't have to go fully functional to benefit from them!

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
conclusion 82
recap - function_ref

Non-owning reference to any Callable with a given signature

On the way to standardization, hopefully C++20


Lightweight, trivial for the compiler to optimize
Clearer semantics and higher performance compared to std function
You can start using function_ref today!

P0792
github:TartanLlama/function_ref

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
Thanks!
https://vittorioromeo.info
[email protected]
[email protected]
@supahvee1234
https://github.com/SuperV1234/cpponsea2019

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
Extras

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.
"compile-time iteration with C++20 lambdas"

"P0573R2: Abbreviated Lambdas for Fun and Profit" (by Barry Revzin & Tomasz Kamiński)

vittorioromeo.info | [email protected] | [email protected] | @supahvee1234 | (C) 2019 Bloomberg Finance L.P. All rights reserved.

You might also like