Skip to content

igor-dyrov/Boost.Phoenix

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 

Repository files navigation

Boost.Phoenix

Boost.Phoenix - это самая важная Boost библиотека функционального программирования. Если библиотеки вроде Boost.Bind и Boost.Lambda предоставляют некоторые дополнительные возможности для функционального программирования, то Boost.Phoenix не только включает в себя эти возможности, но и выходит за их рамки.

В функциональном программировании функции являются объектами и могут быть использованы в качетстве объектов. Используя Boost.Phoenix,можно написать функцию, которая может вернуть другую как рузультат. Также Вы можете отправить функцию параметром в другую функцию. Стало возможным различать создание объекта функции и ее прямой вызов. Представление функции как объекта не эквивалентно ее исполнению.

Boost.Phoenix поддерживает функциональное программирование с использванием объектов функций: функции - это объекты классов, у которых перегружнен оператор operator(). Тогда функциональные объекты ведут себя как другие объекты в C++. Например, они могут быть скопированы и сохранены в контейнере. Однако они также ведут себя как функции, так как их можно вызвать.

Функциональное программирование не внове в C++. Вы можете передать функцию в качестве параметра в другую функцию, не используя Boost.Phoenix.

Пример 39.1. Предикаты на примере лямбда функции и функции Phoenix.

#include <boost/phoenix/phoenix.hpp>
#include <vector>
#include <algorithm>
#include <iostream>

bool is_odd(int i) { return i % 2 == 1; }

int main()
{
  std::vector<int> v{1, 2, 3, 4, 5};

  std::cout << std::count_if(v.begin(), v.end(), is_odd) << '\n';

  auto lambda = [](int i){ return i % 2 == 1; };
  std::cout << std::count_if(v.begin(), v.end(), lambda) << '\n';

  using namespace boost::phoenix::placeholders;
  auto phoenix = arg1 % 2 == 1;
  std::cout << std::count_if(v.begin(), v.end(), phoenix) << '\n';
}

Пример 39.1 показывает использование алгоритма std::count_if () для счета нечетных чисел в векторе v. std::count_if() вызывают три раза, один раз со стандратным предикатом, один раз с лямбда функцией, и один раз с Phoenix функцией.

Phoenix функция отличается от обычной функции и лямбда функции тем, что у нее нет всем привычной структуры. В то время как у других двух функций есть функциональный заголовок с названием, Phoenix функция состоит только из тела функции.

Ключевой компонент Phoenix функции - boost::phoenix::placeholders::arg1. arg1 - глобальный экземпляр функционального объекта. Вы можете использовать его как std::cout : эти объекты создаются, как только соответствующий заголовочный файл объявлен.

arg1 используется, чтобы определить унарную функцию. Выражения arg1 % 2 == 1 создает новую функцию, которая принимает один параметр. Функция сразу не выполняется, но сохранеется в phoenix. phoenix передан в std::count_if(), который вызывает предикат для каждого числа в v.

arg1 - место для передаваемого значения. Как только передается arg1, создается унарная функция. Boost.Phoenix предоставляет дополнительные параметры, такие как boost::phoenix::placeholders::arg2 и boost::phoenix::placeholders::arg3. Функция Phoenix всегда ожидает число параметров, равное самому большому индексу всех пераметров.

В Примере 39.1 3 выводится трижды на экран.

Пример 39.2. Лямбда-функция и Phoenix.

#include <boost/phoenix/phoenix.hpp>
#include <vector>
#include <algorithm>
#include <iostream>

int main()
{
  std::vector<int> v{1, 2, 3, 4, 5};

  auto lambda = [](int i){ return i % 2 == 1; };
  std::cout << std::count_if(v.begin(), v.end(), lambda) << '\n';

  std::vector<long> v2;
  v2.insert(v2.begin(), v.begin(), v.end());

  using namespace boost::phoenix::placeholders;
  auto phoenix = arg1 % 2 == 1;
  std::cout << std::count_if(v.begin(), v.end(), phoenix) << '\n';
  std::cout << std::count_if(v2.begin(), v2.end(), phoenix) << '\n';
}

Пример 39.2 демонстрирует ключевое различие между лямбда функцией и Phoenix функцией. В дополнение к тому, что при объявлении Phoenix функции не требуется писать список параметров, у параметров Phoenix функции нет типов. Лямбда функция lambda ожидает параметр типа int. Phoenix функция примет любой тип, который может быть обработан указанным оператором.

Представляйте себе Phoenix функции как шаблоны функций. Как шаблоны функций, Phoenix функции могут принять любой тип. Это позволяет в Примере 39.2, использовать Phoenix в качестве предиката для контейнеров v и v2 даже при том, что они хранят числа различных типов. При попытке использования lambda как предиката с вектором v2 Вы получите ошибку компилятора.

Пример 39.3. Phoenix функция как отложенный код C++.

#include <boost/phoenix/phoenix.hpp>
#include <vector>
#include <algorithm>
#include <iostream>

int main()
{
  std::vector<int> v{1, 2, 3, 4, 5};

  using namespace boost::phoenix::placeholders;
  auto phoenix = arg1 > 2 && arg1 % 2 == 1;
  std::cout << std::count_if(v.begin(), v.end(), phoenix) << '\n';
}

Пример 39.3 демонстрирует работу функции Phoenix в качестве предиката для подсчета нечетных чисел, превосходящих 2. Функция обращается к arg1 дважды: первый раз, чтобы определить, превосходит ли число двойку и нечетно ли оно. Условия соединены при помощи &&.

Функции Phoenix не выполняются сразу. Функция Phoenix в Примере 39.3 похожа на условие, которое использует многократно логические и арифметическе операторы. Однако условие сразу не выполненяется. Оно выполняется при вызове из std::count_if. Вызов функции в std::count_if() происходит стандартным путем.

Пример 39.3 выводит 2 в стандартный поток.

Пример 39.4. Явные Phoenix типы.

#include <boost/phoenix/phoenix.hpp>
#include <vector>
#include <algorithm>
#include <iostream>

int main()
{
  std::vector<int> v{1, 2, 3, 4, 5};

  using namespace boost::phoenix;
  using namespace boost::phoenix::placeholders;
  auto phoenix = arg1 > val(2) && arg1 % val(2) == val(1);
  std::cout << std::count_if(v.begin(), v.end(), phoenix) << '\n';
}

Пример 39.4 использования явных типов для разных операндов в Phoenix функции. Грубо говоря, Вы не видите типы, только вспомогательную функцию boost::phoenix::val(). Эта функция возвращает объект, инициализированный значением, переданным в boost::phoenix::val(). Фактический тип функционального объекта не имеет значения. Важно то, что Boost.Phoenix перегружает операторы вроде >, &&, % и == для разных типов. Таким образом условия сразу не проверяются. Вместо этого функциональные объекты объединяются для создания более мощных функциональных объектов. В зависимости от операндов они могут автоматически использоваться в качестве функциональных объектов. Или же Вы можете вызвать вспомогательную функцию типа val().

Пример 39.5. boost::phoenix::placeholders::arg1 и boost::phoenix::val().

#include <boost/phoenix/phoenix.hpp>
#include <iostream>

int main()
{
  using namespace boost::phoenix::placeholders;
  std::cout << arg1(1, 2, 3, 4, 5) << '\n';

  auto v = boost::phoenix::val(2);
  std::cout << v() << '\n';
}

Пример 39.5 показывает работу arg1 и val(). arg1 - экземпляр функционального объекта. Его можно использовать непосредственно и вызывать как функцию. Вы можете передать столько параметров, сколько Вам хочется – arg1 возвращает первый.

val() является функцией для создания экземпляра функционального объекта. Функциональный объект инициализируется значением, переданным в качестве параметра. Если к экземпляру обращаются как к функции, возращается значение.

Пример 39.5 выводит на экран 1 и 2.

Пример 39.6. Создание своей Phoenix функции.

#include <boost/phoenix/phoenix.hpp>
#include <vector>
#include <algorithm>
#include <iostream>

struct is_odd_impl
{
    typedef bool result_type;

    template <typename T>
    bool operator()(T t) const { return t % 2 == 1; }
};

boost::phoenix::function<is_odd_impl> is_odd;

int main()
{
  std::vector<int> v{1, 2, 3, 4, 5};

  using namespace boost::phoenix::placeholders;
  std::cout << std::count_if(v.begin(), v.end(), is_odd(arg1)) << '\n';
}

В Примере 39.6 объясняется, как создается собственная Phoenix функция. Вы передаете функциональный объект в шаблонный объект boost::phoenix::function. В примере передается класс is_odd_impl. Этот класс перегружает оператор operator(): когда передается нечетное число, оператор возвращает true. В ином случае оператор возвращает false.

Обратите внимание на то, что Вы должны определить тип result_type. Boost.Phoenix использует его, чтобы определить тип возвращаемого значения оператора operator().

is_odd() является функцией, которую Вы можете использовать как val(). Обе функции возвращают функциональный объект. При вызове параметры передаются в operator(). В Примере 39.6 это означает, что std::count_if() по-прежнему считает нечетные числа.

Пример 39.7. Преобразование стандартных функций в Phoenix функцию

#include <boost/phoenix/phoenix.hpp>
#include <vector>
#include <algorithm>
#include <iostream>

bool is_odd_function(int i) { return i % 2 == 1; }

BOOST_PHOENIX_ADAPT_FUNCTION(bool, is_odd, is_odd_function, 1)

int main()
{
  std::vector<int> v{1, 2, 3, 4, 5};

  using namespace boost::phoenix::placeholders;
  std::cout << std::count_if(v.begin(), v.end(), is_odd(arg1)) << '\n';
}

Если Вы хотите преобразовать обычную функцию в Phoenix функцию, Вы можете действовать как в Примере 39.7. Вам не обязательно определять функциональный объект, как в предыдущем примере.

Используйте макрос BOOST_PHOENIX_ADAPT_FUNCTION, чтобы преобразовать обычную функцию в Phoenix функцию. Передайте тип возвращаемого значения, имя функции и количество параметров в макрос.

Пример 39.8. Phoenix функции и boost::phoenix::bind()

#include <boost/phoenix/phoenix.hpp>
#include <vector>
#include <algorithm>
#include <iostream>

bool is_odd(int i) { return i % 2 == 1; }

int main()
{
  std::vector<int> v{1, 2, 3, 4, 5};

  using namespace boost::phoenix;
  using namespace boost::phoenix::placeholders;
  std::cout << std::count_if(v.begin(), v.end(), bind(is_odd, arg1)) << '\n';
}

Чтобы использовать обычную функцию в качестве Phoenix функции, Вы также можете использовать boost::phoenix::bind(), как в Примере 39.8. boost::phoenix::bind() работает, как std::bind(). Имя обычной функции передается как первый параметр. Все остальные параметры передаются первоначальной функции.

#Совет Избегайте boost::phoenix::bind(). Создавайте свои собственные Phoenix функции. Это приведет к большему количеству читаемого кода. Особенно в случае со сложными выражениями, где придется возиться с дополнительными деталями boost::phoenix::bind().

Пример 39.9. Произвольные сложные Phoenix функции.

#include <boost/phoenix/phoenix.hpp>
#include <vector>
#include <algorithm>
#include <iostream>

int main()
{
  std::vector<int> v{1, 2, 3, 4, 5};

  using namespace boost::phoenix;
  using namespace boost::phoenix::placeholders;
  int count = 0;
  std::for_each(v.begin(), v.end(), if_(arg1 > 2 && arg1 % 2 == 1)
    [
      ++ref(count)
    ]);
  std::cout << count << '\n';
}

Boost.Phoenix предоставляет некоторые функциональные объекты, которые имитируют ключевые слова C++. Например, Вы можете использую функцию boost::phoenix::if_() (см. Пример 39.9) создать функциональный объект, который действует так же, как if и проверяет условие. Если условие выполняется, код передается в функциональный объект с operator[]. Конечно, этот код тоже должен основываться на функциональных объектах. Таким образом Вы можете создать сложные Phoenix функции.

Пример 39.9 увеличивает count каждый раз, когда встречается нечетное число больше 2. Чтобы использовать инкрементный оператор для count, count обернуто в функциональный объект с помощью *** boost::phoenix::ref(). В отличие от boost::phoenix::val(), никакое значение не копируется в функциональный объект. Функциональный объект, возвращенный *** boost::phoenix::ref() хранит ссылку, в данном случае - на count.

#Совет Не используйте Boost.Phoenix, чтобы создавать сложные функции. Лучше использовать лямба функции из C++ 11. Несмотря на то, что Boost.Phoenix похож на синтаксис C++, использование ключевых слов вроде if_ или больших блоков кода между квадратными скобками вряд ли улучшает читабельность rjlf.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published