Функциональное программирование в C++: от лямбд до алгоритмов

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Функциональное программирование в C++: от лямбд до композиции

Функциональное программирование (ФП) — это парадигма, в которой вычисления строятся на применении чистых функций, неизменяемости данных и отсутствии побочных эффектов. Хотя C++ изначально был мультипарадигменным языком, его современные стандарты (C++11/14/17/20) внедрили мощные инструменты для ФП: лямбда-выражения, std::function, алгоритмы STL, корутины и даже поддержку монадических паттернов. В этой статье мы разберём, как писать функциональный код на C++, не теряя производительности.

1. Основы функционального стиля: лямбды и std::function

Лямбда-выражения — сердце ФП в C++. Они позволяют создавать анонимные функции «на лету», захватывая контекст.

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5};
    
    // Лямбда для фильтрации чётных чисел
    auto is_even = [](int x) { return x % 2 == 0; };
    
    // Использование с алгоритмом std::copy_if
    std::vector<int> evens;
    std::copy_if(nums.begin(), nums.end(), 
                 std::back_inserter(evens), is_even);
                 
    for (int n : evens) std::cout << n << " "; // 2 4
    return 0;
}

Ключевые моменты лямбд:

  • Захват — [=] (по значению), [&] (по ссылке), [this] (для методов класса).
  • Тип — каждая лямбда имеет уникальный тип, поэтому для передачи в функцию используют std::function или шаблоны.
  • Возвращаемое значение — выводится автоматически, если не указано иное.

std::function — обёртка для callable-объектов (функции, лямбды, функторы). Удобна для хранения и передачи, но добавляет накладные расходы (виртуальный вызов).

#include <functional>

void apply_twice(std::function<int(int)> f, int x) {
    std::cout << f(f(x)) << std::endl; // Композиция
}

int main() {
    auto square = [](int x) { return x * x; };
    apply_twice(square, 3); // (3^2)^2 = 81
    return 0;
}

2. Алгоритмы STL как основа функциональных цепочек

STL-алгоритмы (std::transform, std::copy_if, std::accumulate и др.) идеально ложатся на функциональный стиль. Они не изменяют контейнеры напрямую, а принимают итераторы и возвращают результаты.

Пример: фильтрация и преобразование (map/filter)

#include <numeric>
#include <ranges> // C++20

int main() {
    std::vector<int> data = {1, 2, 3, 4, 5, 6};
    
    // Классический подход (C++11)
    std::vector<int> filtered;
    std::copy_if(data.begin(), data.end(), 
                 std::back_inserter(filtered), 
                 [](int x) { return x > 3; });
    
    std::vector<int> result;
    std::transform(filtered.begin(), filtered.end(),
                   std::back_inserter(result),
                   [](int x) { return x * 2; });
    
    // Современный подход с ranges (C++20)
    auto view = data 
                | std::views::filter([](int x) { return x > 3; })
                | std::views::transform([](int x) { return x * 2; });
    
    for (int v : view) std::cout << v << " "; // 8 10 12
    return 0;
}

Свёртка (fold) с std::accumulate

#include <numeric>

int main() {
    std::vector<int> nums = {1, 2, 3, 4};
    
    // Сумма с начальным значением 0
    int sum = std::accumulate(nums.begin(), nums.end(), 0, 
                              [](int a, int b) { return a + b; });
    
    // Произведение с начальным значением 1
    int prod = std::accumulate(nums.begin(), nums.end(), 1, 
                               std::multiplies<int>());
    
    std::cout << "Sum: " << sum << ", Prod: " << prod; // Sum: 10, Prod: 24
    return 0;
}

3. Композиция функций и каррирование

Композиция — объединение двух или более функций в одну. В C++ её можно реализовать через лямбды или вспомогательные шаблоны.

#include <functional>

// Вспомогательная функция для композиции
template<typename F, typename G>
auto compose(F f, G g) {
    return [=](auto x) { return f(g(x)); };
}

int main() {
    auto add_one = [](int x) { return x + 1; };
    auto square  = [](int x) { return x * x; };
    
    auto add_then_square = compose(square, add_one);
    std::cout << add_then_square(2); // (2+1)^2 = 9
    
    // С помощью std::bind (менее читаемо)
    auto bind_example = std::bind(square, std::bind(add_one, std::placeholders::_1));
    std::cout << bind_example(2); // 9
    return 0;
}

Каррирование — превращение функции от нескольких аргументов в последовательность функций от одного аргумента. Реализуется через вложенные лямбды:

auto curried_add = [](int a) {
    return [a](int b) {
        return a + b;
    };
};

int main() {
    auto add_five = curried_add(5);
    std::cout << add_five(3); // 8
    return 0;
}

4. Монады и функторы: std

Блоги

Рекомендаций по книгам