C++ Boost Phoenix库示例分析使用

C/C++
281
0
0
2023-06-17
目录
  • 一、说明
  • 二、预先知道Boost.Phoenix
  • 三、示例和代码

一、说明

在函数式编程模型中,函数是对象,与其他对象一样,可以作为参数传递给函数或存储在容器中。有许多支持函数式编程模型的 Boost 库。

  • Boost.Phoenix 是这些库中最广泛、也是最重要的库。它取代了库 Boost.Lambda,它被简要介绍,但只是为了完整性。
  • Boost.Function 提供了一个类,可以轻松定义函数指针,而无需使用源自 C 编程语言的语法。
  • Boost.Bind 是一个适配器,即使实际签名与预期签名不同,它也允许您将函数作为参数传递给其他函数。
  • Boost.Ref 可用于传递对对象的引用,即使函数通过副本传递参数。
  • Boost.Lambda 可以称为 Boost.Phoenix 的前身。它是一个相当古老的库,并且在将 C++11 添加到编程语言之前很多年就允许使用 lambda 函数。

二、预先知道Boost.Phoenix

Boost.Phoenix 是函数式编程最重要的 Boost 库。虽然 Boost.Bind 或 Boost.Lambda 等库为函数式编程提供了一些支持,但 Boost.Phoenix 包含这些库的功能并超越了它们。

在函数式编程中,函数是对象,可以像对象一样处理。使用 Boost.Phoenix,一个函数可以返回另一个函数作为结果。也可以将一个函数作为参数传递给另一个函数。因为函数是对象,所以可以区分实例化和执行。访问一个函数不等于执行它。

Boost.Phoenix 支持使用函数对象进行函数式编程:函数是基于类的对象,这些类重载了运算符 operator()。这样,函数对象的行为就像 C++ 中的其他对象一样。例如,它们可以被复制并存储在容器中。但是,它们的行为也类似于函数,因为它们可以被调用。

函数式编程在 C++ 中并不新鲜。您可以将一个函数作为参数传递给另一个函数,而无需使用 Boost.Phoenix。

三、示例和代码

示例 39.1。谓词作为全局函数、lambda 函数和 Phoenix 函数

#include <boost/phoenix/phoenix.hpp>
#include <vector>
#include <algorithm>
#include <iostream>
bool is_odd(int i) { return i % == 1; }
int main()
{
  std::vector<int> v{, 2, 3, 4, 5};
  std::cout << std::count_if(v.begin(), v.end(), is_odd) << '\n';
  auto lambda = [](int i){ return i % == 1; };
  std::cout << std::count_if(v.begin(), v.end(), lambda) << '\n';
  using namespace boost::phoenix::placeholders;
  auto phoenix = arg % 2 == 1;
  std::cout << std::count_if(v.begin(), v.end(), phoenix) << '\n';
}

Example39.1

示例 39.1 使用算法 std::count_if() 来计算向量 v 中的奇数。std::count_if() 被调用 3 次,一次使用谓词作为独立函数,一次使用 lambda 函数,一次使用凤凰功能。

Phoenix 函数与独立函数和 lambda 函数不同,因为它没有框架。虽然其他两个函数有一个带有签名的函数头,但 Phoenix 函数似乎只包含一个函数体。

Phoenix 函数的关键组件是 boost::phoenix::placeholders::arg1。 arg1 是函数对象的全局实例。您可以像 std::cout 一样使用它:一旦包含相应的头文件,这些对象就会存在。

arg1 用于定义一元函数。表达式 arg1 % 2 == 1 创建一个需要一个参数的新函数。该函数不会立即执行,而是存储在 Phoenix 中。 phoenix 被传递给 std::count_if() ,它为 v 中的每个数字调用谓词。

arg1 是调用 Phoenix 函数时传递的值的占位符。由于此处仅使用了 arg1,因此创建了一个一元函数。 Boost.Phoenix 提供了额外的占位符,例如 boost::phoenix::placeholders::arg2 和 boost::phoenix::placeholders::arg3。 Phoenix 函数总是期望与具有最大数量的占位符一样多的参数。

Example39.1

示例 39.1 将 3 写入标准输出 3 次。

示例 39.2。 Phoenix 函数与 lambda 函数

#include <boost/phoenix/phoenix.hpp>
#include <vector>
#include <algorithm>
#include <iostream>
int main()
{
  std::vector<int> v{, 2, 3, 4, 5};
  auto lambda = [](int i){ return i % == 1; };
  std::cout << std::count_if(v.begin(), v.end(), lambda) << '\n';
  std::vector<long> v;
  v.insert(v2.begin(), v.begin(), v.end());
  using namespace boost::phoenix::placeholders;
  auto phoenix = arg % 2 == 1;
  std::cout << std::count_if(v.begin(), v.end(), phoenix) << '\n';
  std::cout << std::count_if(v.begin(), v2.end(), phoenix) << '\n';
}

Example39.2

例 39.2 强调了 Phoenix 和 lambda 函数之间的一个关键区别。除了不需要带有参数列表的函数头之外,Phoenix 函数参数没有类型。 lambda 函数 lambda 需要一个 int 类型的参数。 Phoenix 函数 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{, 2, 3, 4, 5};
  using namespace boost::phoenix::placeholders;
  auto phoenix = arg > 2 && arg1 % 2 == 1;
  std::cout << std::count_if(v.begin(), v.end(), phoenix) << '\n';
}

Example39.3

示例 39.3 使用 Phoenix 函数作为谓词,使用 std::count_if() 计算大于 2 的奇数。Phoenix 函数访问 arg1 两次:一次测试占位符是否大于 2,一次测试它是否为奇数.条件与 && 相关联。

您可以将 Phoenix 函数视为不会立即执行的 C++ 代码。示例 39.3 中的 Phoenix 函数看起来像一个使用多个逻辑和算术运算符的条件。但是,条件不会立即执行。它仅在从 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{, 2, 3, 4, 5};
  using namespace boost::phoenix;
  using namespace boost::phoenix::placeholders;
  auto phoenix = arg > val(2) && arg1 % val(2) == val(1);
  std::cout << std::count_if(v.begin(), v.end(), phoenix) << '\n';
}

Example39.4

例 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 << arg(1, 2, 3, 4, 5) << '\n';
  auto v = boost::phoenix::val();
  std::cout << v() << '\n';
}

Example39.5

示例 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 % == 1; }
};
boost::phoenix::function<is_odd_impl> is_odd;
int main()
{
  std::vector<int> v{, 2, 3, 4, 5};
  using namespace boost::phoenix::placeholders;
  std::cout << std::count_if(v.begin(), v.end(), is_odd(arg)) << '\n';
}

Example39.6

示例 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 % == 1; }
BOOST_PHOENIX_ADAPT_FUNCTION(bool, is_odd, is_odd_function,)
int main()
{
  std::vector<int> v{, 2, 3, 4, 5};
  using namespace boost::phoenix::placeholders;
  std::cout << std::count_if(v.begin(), v.end(), is_odd(arg)) << '\n';
}

如果要将独立函数转换为 Phoenix 函数,可以按照示例 39.7 进行操作。您不必像前面的示例中那样定义函数对象。

您可以使用宏 BOOST_PHOENIX_ADAPT_FUNCTION 将独立函数转换为 Phoenix 函数。将返回值的类型、要定义的 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 % == 1; }
int main()
{
  std::vector<int> v{, 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, arg)) << '\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{, 2, 3, 4, 5};
  using namespace boost::phoenix;
  using namespace boost::phoenix::placeholders;
  int count =;
  std::for_each(v.begin(), v.end(), if_(arg > 2 && arg1 % 2 == 1)
    [
      ++ref(count)
    ]);
  std::cout << count << '\n';
}

Boost.Phoenix 提供了一些模拟 C++ 关键字的函数对象。例如,您可以使用函数 boost::phoenix::if_()(参见示例 39.9)创建一个函数对象,该对象的行为类似于 if 并测试条件。如果条件为真,则使用 operator[] 传递给函数对象的代码将被执行。当然,该代码也必须基于函数对象。这样,您可以创建复杂的 Phoenix 函数。

示例 39.9 对每个大于 2 的奇数进行增量计数。要对 count 使用增量运算符,请使用 boost::phoenix::ref() 将 count 包装在一个函数对象中。与 boost::phoenix::val() 相比,没有值被复制到函数对象中。 boost::phoenix::ref() 返回的函数对象存储了一个引用——这里是对 count 的引用。

小提示

不要使用 Boost.Phoenix 创建复杂的函数。最好使用 C++11 中的 lambda 函数。虽然 Boost.Phoenix 接近 C++ 语法,但使用 if_ 等关键字或方括号之间的代码块并不一定会提高可读性。