Funkcje, predykaty i obiekty funkcyjne

Funkcje

Wiele algorytmów standardowych wymaga przekazania adresu funkcji jako argumentu.

void print_element(int value)
{
   std::cout << "Item: " << value << '\n';
}

std::list<int> lst = { 1, 2, 3, 4, 5 };
std::for_each(lst.begin(), lst.end(), &print_element);

Predykaty

Predykat - funkcja, która zwraca wartość logiczną bool (true/false) lub int

bool is_leap_year(int year)
{
    if ( 0 == year % 400 ) return true;
    if ( 0 == year % 100 ) return false;
    if ( 0 == year % 4 ) return true;
    return false;
}

auto first_leap =
    std::find_if(lst.begin(), lst.end(), &is_leap_year);

Obiekt funkcyjny - funktor

  • Obiekt będący instancją klasy definiującej operator() (wywołania funkcji)
  • Może być wywołany za pomocą tej samej składni co funkcja
class BiggerThan
{
    const int test_value_;
public:
    BiggerThan (int x) : test_value_{x} {}

    bool operator () (int val) const
    {
        return val > test_value_;
    }
};

auto pos = std::find_if(lst.begin(), lst.end(), BiggerThan(12) );

Funktory arytmetyczne i logiczne

Funktory arytmetyczne i logiczne
proste szablony klas, przeciążające operator ( ), tak żeby wykonywały one operację logiczną lub arytmetyczną.

Dwuargumentowe funktory:

  • arytmetyczne
    • divides (/)
    • minus (-)
    • modulus (%)
    • multiplies (*)
    • plus (+)
  • logiczne
    • logical_and (&&)
    • logical_or (||)
  • bitowe
    • bit_and (x & y)
    • bit_or (x | y)
    • bit_xor (x ^ y)
vector<int> data1 = { 1, 2, 3 };
vector<int> data2 = { 3, 2, 1 };

vector<int> sum(data1.size());

transform(data1.begin(), data1.end(), data2.begin(), sum.begin(), plus<int>{});
transform(data1.begin(), data1.end(), data2.begin(), sum.begin(), plus<>{}); // C++14

Jednoargumentowe funktory:

  • negate (-)
  • logical_not (!)
  • bit_not (~)

Funktory porównujące

Funktory porównujące - proste szablony klas, których przeciążony operator ( ) wykonuje porównanie relacji lub równości.

  • equal_to (==)
  • greater (>)
  • greater_equal (>=)
  • less (<)
  • less_equal (<=)
  • not_equal_to (!=)
vector<int> vec = { 5, 21, 3, 1, 8, 9 };

sort(vec.begin(), vec.end(), greater<int>{});

Wrapper - std::mem_fn

std::mem_fn umożliwia utworzenie obiektu funkcyjnego, który przechowuje wskaźnik do metody klasy. Przekazanie referencji, wskaźnika lub smart-pointer’a do obiektu jako argumentu wywołania powoduje wywołanie odpowiedniej metody dla przekazanego obiektu.

class Person
{
    std::string name_;
public:
    explicit Person(const std::string& name) : name_{name}
    {}

    void print() const
    {
        cout << "Person: " << name_ << endl;
    }
};

Person p{ "John" };

auto name_printer =  std::mem_fn(&Person::print);
name_printer(p); //
name_printer(&p);

auto ptr_p = std::make_shared<Person>("Adam");
name_printer(ptr_p);

Funktor mem_fn jest często wykorzystywany w algorytmach STL:

std::vector<Person> stuff = { Person{"John"}, Person{"Adam"}, Person{"Maria"} };

std::for_each(stuff.begin(), stuff.end(), std::mem_fn(&Person::print);

Wyrażenia lambda

Wyrażenie lambda to definicja „w miejscu” obiektu funkcyjnego, który można następnie użyć jako zmienną lub argument wywołania funkcji (np. algorytmu standardowego).

Minimalne wyrażenie lambda:

[] { std::cout << "hello world" << std::endl; }();

wyrażenia lambda mogą przyjmować parametry, a także zwracać wartość:

auto l = [] (int x, int y) {
    return x + y;
};

int result = l(2,3) // 5

Wewnątrz nawiasów klamrowych możemy zawrzeć elementy, które lambda ma przechwycić z zakresu w którym jest tworzona.

  • [=] zakres (scope) jest przekazany przez wartość.
    Wewnątrz lambdy możemy odczytać wartość zmiennych zewnętrznych taką, jaka była w momencie tworzenia lambdy (domknięcie).
  • [&] zakres (scope) jest przekazany przez referencję.
    Lambda ma dostęp do odczytu i zapisu zmiennych z zakresu w którym została utworzona.
vector<int> vec = { 1, 2, 3, 4, 5 };

const int value = 5;

auto add5 = [=](int x) {
    return x + value;
};

transform(vec.begin(), vec.end(), vec.begin(), add5);
int sum{};
for_each(vec.begin(), vec.end(), [&sum](int x) { sum += x; });

Typ domknięcia dla wyrażenia lambda określić używając słowa auto:

auto l = [] (int x, int y) {
    return x + y;
};

Funkcje mogą zwracać lambdy. Wymagane jest wówczas określenie typu zwracanego jako:

  • std::function - w C++11

    std::function<int()> create_generator(size_t seed)
    {
        return [seed]() mutable { return ++seed; };
    }
    
  • auto w C++14

    auto create_generator(size_t seed)
    {
        return [seed]() mutable { return ++seed; };
    }
    

Użycie funkji create_generator():

vector<int> vec(10);

generate_n(vec.begin(), 10, create_generator(42));