Functional C++

14 Mar 2017 in slides
Slide Show

Functional C++

section

Contents

  • C++ 98
  • lambda functions
  • std::function
  • signals and slots

C-style

void qsort(void* pointer, size_t count, size_t size,
    int (*comparator) (const void*, const void*));
int greater(const void* lhs, const void* rhs) {
	const int* l = static_cast<const int*>(lhs);
	const int* r = static_cast<const int*>(rhs);
	return *r - *l;
}
// ...
std::qsort(numbers, 5, sizeof(int), &greater);
  • does not work for non-POD
  • needs a new function
  • C++'98 does not allow defining functions inside functions
  • the function does not allow to store state, unless it is global
  • the function has to always be called indirectly through a pointer
    • and will get called O(NlogN) times for sorting N numbers

C++'98 style

void sort(T begin, T end, C comparator);
  • works for everything that has operator <
  • needs a new function or functor
  • you can store state
  • the function can be inlined and optimized
template <typename T, typename P>
T partition(T begin, T end, P unary_predicate);

// partitions a range so that all X for which predicate(X) is true
// precede all Y for which predicate(Y) is false
template <typename T>
struct less_than_t
{
    explicit less_than_t(T value)
        : _value(value)
    {}

    bool operator()(T x) const {
        return x < _value;
    }

    T _value;
};

template <typename T>

less_than_t<T> less_than(T x)
{
    return less_than_t<T>(x);
}
std::partition(numbers, numbers + size, less_than(5));
  • it is quite a lot of code
  • struct can be defined inside a function
    • but such structs can not be used for instantiating templates
      • the standard and g++ say so
      • Visual Studio does not

C++11 style

lambda

lambda

Anonymous functions that can be defined inside functions and can access variables defined outside the anonymous functions.

lambda expressions

[capture](parameters) -> return_type { function_body }
std::vector<int> v;
std::partition(v.begin(), v.end(), [](int x) {
    return x % 2 == 0;
});
std::for_each(v.begin(), v.end(), [&](int x) {
    std::cout << x << std::endl;
});

lambda captures

  • [] - no variables are captured
  • [x] - x is captured by value
  • [&x] - x is captured by ref
  • [&] - any external variable is implicitly captured by ref
  • [=] - any external variable is implicitly captured by value
  • [x, &y] - x is captured by value, y - by reference

Captures

  • pointers to lambda functions without captured variables are the same as pointer to non-member functions
  • this can be captured only by value and only inside methods

Implementation of lambda functions

Most compilers generate a struct with a special name and operator()

  • no captures - might generate a non-member function
  • captures - the captured variables are members of the struct

The future for lambda functions

Generic lambdas

auto dbl = [](auto x) { return x + x; };

std::vector<int> vi;
std::transform(vi.begin(), vi.end(), vi.begin(), dbl);

std::vector<std::string> vs;
std::transform(vs.begin(), vs.end(), vs.begin(), dbl);

Initializers

std::vector<int> vi;
std::tranform(vi.begin(), vi.end(), vi.begin(),
[value = 42](auto x) {
    return x + value;
});

Capture by move

auto ptr = std::make_unique<int>(42);
std::vector<int> vi;
std::tranform(vi.begin(), vi.end(), vi.begin(),
    [ptr = std::move(ptr)](auto x) {
        return x + *ptr;
});

Functions as first class citizen

std::vector<int> lengths;
std::vector<string> strings;

lengths.resize(strings.size());
std::transform(strings.begin(), strings.end(), lengths.begin(),
    // ???
);
std::transform(strings.begin(), strings.end(), lengths.begin(),
    std::string::length // ???
);
// No

Functions in C++

  • free functions

  • methods

    free_function(a, 42);

    object.method(42);

size_t get_length(const std::string& s) {
    return s.length();
}

std::transform(strings.begin(), strings.end(), lengths.begin(),
    get_length;
);

C++98

std::transform(strings.begin(), strings.end(), lengths.begin(),
    std::mem_fun_ref(&std::string::length);
);

std::mem_fun

  • works only for methods without arguments or a single argument
  • the object has to be supplied via a pointer
  • std::mem_fun_ref works with a reference
  • DEPRECATED

Currying

std::bind1st and std::bind2nd

Bind the first (second) argument of a function to a fixed value and return a function with one argument less.

DEPRECATED

std::vector<std::string> v;
std::transform(v.begin(), v.end(), v.begin(),
    std::bind1st(std::plus<std::string>(), ";"));
// "x" -> ";x"

std::transform(v.begin(), v.end(), v.begin(),
    std::bind2nd(std::plus<std::string>(), ";"));
// "x" -> "x;"

C++11

std::mem_fn

Generalized version of std::mem_fun.

  • handles an arbitrary number of arguments
  • the object can be passed by reference, plain or smart pointer.
typedef std::vector<std::shared_ptr<Players>> Players;
void Transform(Players& players, const Transform& t)
{
    std::for_each(players.begin(), players.end(),
                  std::mem_fn(&Player::TransformWith));
}

std::bind

std::bind

  • binding any argument of a function to a fixed value
  • reordering of arguments

std::bind

  • former boost::bind - boost version is still boosted with extra features
template <typename Callback>
std::shared_ptr<Button> make_button(Callback callback);

struct Application {
    void Quit();
};

Application app;
auto quit = make_button(std::bind(&Application::Quit, &app));

Be sure that the quit button will not be clicked after app is destroyed.

std::shared_ptr<Application> app;
auto quit = make_button(std::bind(&Application::Quit, app));

Arguments

auto autosave = make_checkbox(std::bind(&Application::Autosave, app,
                                        std::placeholders::_1));

C# delegates in C++.

void Function(std::string s, int n, Person p) {
    std::cout << p.name() << ": " << s;
    while (n-- > 0)
        std::cout << '!';
    std::cout << std::endl;
}

Person p("Yoda");
auto mega_fun = std::bind(&Function, _2, _1, &p);

mega_fun(3, "The Answer is 42");
// Yoda: The answer is 42!!!

std::function

std::function is a generic function object.

Functions in C++

Let me count the ways ...

Free functions

void free_function(int x) { std::cout << "free function: " << x << std::endl; }
// ...
void free_with_ud(const UserDefined& ud, int x)
{
    std::cout << "free with ud " << &ud << ": " << x << std::endl;
}
// ...
free_function(42);

Functors

struct Functor
{
    void operator()(int x) const
    {
        std::cout << "functor " << this << ": " << x << std::endl;
    }
};
// ...
Functor functor;
functor(42);

Methods

struct UserDefined
{
    void method(int x) const
    {
        std::cout << "method " << this << ": " << x << std::endl;
    }

    static void static_method(int x)
    {
        std::cout << "same as free but with access to internals" << std::endl;
    }
};
// ...
UserDefined ud;
ud.method(42);

Pointer to functions

typedef void (*FreeFunctionPtr)(int);
FreeFunctionPtr ffp = &free_function;
ffp(42);

typedef void (UserDefined::*MethodPtr)(int) const;

MethodPtr mp = &UserDefined::method;
(ud.*mp)(42);
UserDefined* udp = &ud;
(udp->*mp)(42);

std::function

std::function<void(int)> generic = free_function;
generic(24);

generic = functor;
generic(2);

std::function with types

std::function<void(const UserDefined&, int)> ud_function =
    &UserDefined::method;

ud_function(ud, 42);

ud_function = &free_with_ud;
ud_function(ud, 42);

std::function std::bind

generic = std::bind(&UserDefined::method, ud, _1);
generic(4);

generic = std::bind(&UserDefined::method, std::ref(ud), _1);
generic(8);

Functions and tuples

Functions in C++11

template <typename T>
T plus(T lhs, T rhs) {
    return lhs + rhs;
}
auto s = plus(2, 2);
Person p1;
Person p2;
auto s = plus(p1, p2)
struct Person
{
    Household operator+(const Person&);
}
??? plus(Person lhs, Person rhs) {
    return lhs + rhs;
}
auto plus(Person lhs, Person rhs) -> decltype(lhs + rhs)
{
    return lhs + rhs;
}

Functions in C++14

??? plus(Person lhs, Person rhs)
{
    return lhs + rhs;
}
auto plus(Person lhs, Person rhs)
{
    return lhs + rhs;
}
  • all returns must have the same type
    • single return