Но достоинства функционального программирования проявляются не только в языках, где эта парадигма применяется по умолчанию. С++ — мультипарадигменный язык, и на нем, безусловно, можно писать программы в стиле ФП. С появлением в С++11 лямбда-функций (см. приложение А, раздел А.6), включением шаблона std::bind из Boost и TR1 и добавлением автоматического выведения типа переменных (см. приложение А, раздел А.7) это стало даже проще, чем в С++98. Будущие результаты — это последний элемент из тех, что позволяют реализовать на С++ параллелизм в стиле ФП; благодаря передаче будущих результатов результат одного вычисления можно сделать зависящим от результата другого
Чтобы продемонстрировать использование будущих результатов при написании параллельных программ в духе ФП, рассмотрим простую реализацию алгоритма быстрой сортировки Quicksort. Основная идея алгоритма проста: имея список значений, выбрать некий опорный элемент и разбить список на две части — в одну войдут элементы, меньшие опорного, в другую — большие или равные опорному. Отсортированный список получается путем сортировки обоих частей и объединения трех списков: отсортированного множества элементов, меньших опорного элемента, самого опорного элемента и отсортированного множества элементов, больших или равных опорному элементу. На рис. 4.2 показано, как этот алгоритм сортирует список из 10 целых чисел. В листинге ниже приведена последовательная реализация алгоритма в духе ФП; в ней список принимается и возвращается по значению, а не сортируется по месту в std::sort().
Рис. 4.2. Рекурсивная сортировка в духе ФП
Листинг 4.12. Последовательная реализация Quicksort в духе ФП
template
std::list
if (input.empty()) {
return input;
}
std::list
result.splice(result.begin(), input, input.begin());←(1)
T const& pivot = *result.begin(); ←(2)
auto divide_point = std::partition(input.begin(), input.end(),
[&](T const& t) { return t < pivot; });←(3)
std::list
lower_part.splice(
lower_part.end(), input, input.begin(), divide_point); ←(4)
auto new_lower(
sequential_quick_sort(std::move(lower_part))); ←(5)
auto new_higher(
sequential_quick_sort(std::move(input))); ←(6)
result.splice(result.end(), new_higher); ←(7)
result.splice(result.begin(), new_lower); ←(8)
return result;
}
Хотя интерфейс выдержан в духе ФП, прямое применение ФП привело бы к неоправданно большому числу операций копирования, поэтому внутри мы используем «обычный» императивный стиль. В качестве опорного мы выбираем первый элемент и отрезаем его от списка с помощью функции splice() (1). Потенциально это может привести к неоптимальной сортировке (в терминах количества операций сравнения и обмена), но любой другой подход при работе с std::list может существенно увеличить время за счет обхода списка. Мы знаем, что этот элемент должен войти в результат, поэтому можем сразу поместить его в список, где результат будет храниться. Далее мы хотим использовать этот элемент для сравнения, поэтому берем ссылку на него, чтобы избежать копирования (2). Теперь можно с помощью алгоритма std::partition разбить последовательность на две части: pivot (подробнее о лямбда-функциях см. в разделе А.5 приложения А).
Алгоритм std::partition() переупорядочивает список на месте и возвращает итератор, указывающий на первый элемент, который auto, чтобы компилятор вывел его самостоятельно (см. приложение А, раздел А.7).