Boost C++ 库

……是世界上最受推崇、设计最精良的 C++ 库项目之一。 Herb SutterAndrei Alexandrescu, 《C++ 编码标准》

第 1 章。Boost.Functional/Forward 1.0 - Boost C++ 函数库

第 1 章。Boost.Functional/Forward 1.0

Tobias Schwinger

根据 Boost 软件许可证版本 1.0 发布。(参见随附文件 LICENSE_1_0.txt 或在 https://boost.ac.cn/LICENSE_1_0.txt 复制)

boost::forward_adapter 为函数对象提供了一个可重用的适配器模板。它将右值(RValues)转发为 const 引用,而左值(LValues)则保持不变。

struct g // function object that only accept LValues
{
    template< typename T0, typename T1, typename T2 >
    void operator()(T0 & t0, T1 & t1, T2 & t2) const;

    typedef void result_type;
};

// Adapted version also accepts RValues and forwards
// them as references to const, LValues as-is
typedef boost::forward_adapter<g> f;

另一个适配器 boost::lighweight_forward_adapter 允许通过用户的一些帮助来转发,该适配器接受并解包引用包装器(参见 Boost.Ref)作为引用参数,并对所有其他参数进行 const 限定。

目标函数必须与 Boost.ResultOf 兼容,适配器也是如此。

假设我们有一个函数 f,我们可以这样调用它:

f(123,a_variable);

现在我们想写另一个通用函数 g,它可以像这样调用,并返回一个调用 f 并带有相同参数的对象。

f(123,a_variable) == g(f,123,a_variable).call_f()

我们为什么会想要这样做?

也许我们想多次运行 f。或者我们想在另一个线程中运行它。也许我们只是想暂时封装调用表达式,然后将其与其他允许组合更复杂表达式的代码一起使用,以便使用 C++ 模板对其进行分解,并让编译器生成一些最终在运行时调用 f 的机制(换句话说;应用一种通常称为表达式模板的技术)。

现在,我们该怎么做?

坏消息是:这是不可能的。

这是因为变量与其值表达式之间存在细微差别:给定

int y;
int const z = 0;

and

template< typename T > void func1(T & x);

我们可以调用

func1(y); // x is a reference to a non-const object
func1(z); // x is a reference to a const object

其中

func1(1); // fails to compile.

这样,我们可以安全地让 func1 存储其引用参数,并且编译器阻止我们存储对具有临时生命周期的对象的引用。

重要的是要认识到,非 const 性以及对象是否绑定到非 const 引用参数是两个不同的属性。后者是左值(LValues)和右值(RValues)之间的区别。这些名称源自赋值表达式的左侧和右侧,因此左值通常是你可以赋值的对象,而右值是右侧表达式的临时结果。

y = 1+2;        // a is LValue, 1+2 is the expression producing the RValue,
// 1+2 = a;     // usually makes no sense. 

func1(y);       // works, because y is an LValue
// func1(1+2);  // fails to compile, because we only got an RValue.

如果我们对参数添加 const 限定,我们的函数也将接受右值:

template< typename T > void func2(T const & x);

// [...] function scope:
func2(1); // x is a reference to a const temporary, object,
func2(y); // x is a reference to a const object, while y is not const, and
func2(z); // x is a reference to a const object, just like z.

在所有情况下,func2 中的参数 x 都是 const 限定的左值。我们可以使用函数重载来识别非 const 左值:

template< typename T > void func3(T const & x); // #1
template< typename T > void func3(T & x);       // #2

// [...] function scope:
func3(1); // x is a reference to a const, temporary object in #1,
func3(y); // x is a reference to a non-const object in #2, and
func3(z); // x is a reference to a const object in #1.

请注意,重载函数 func3 中的所有参数 x 都是左值。事实上,在 C++98 中,没有办法将右值原样传递到函数中。另外请注意,我们无法区分曾经是 const 限定左值的值和右值。

这就是我们通过 C++ 98 能够实现的、最接近上述通用转发函数 g 的方法。有关包括需要语言更改的解决方案在内的非常详细的讨论,请参阅 The Forwarding Problem

现在,为了实际实现它,我们需要为每个参数数量(即 2^(Nmax+1) - 2^Nmin)提供 2^N 个重载(每个都有 const 和非 const 限定)。没错,这意味着编译时复杂度是 O(2^N),但由于系数很低,对于合理的参数数量(< 10)来说效果相当好。

说明

函数对象适配器模板,其实例可以用左值和右值参数调用。右值参数被转发为引用到 const 的左值。

可以通过第二个、数值型的非类型模板参数来指定元数(arity),以将转发限制为特定的元数。如果存在第三个、数值型的非类型模板参数,则第二个和第三个模板参数分别被视为最小和最大元数。指定元数有助于提高诊断消息的可读性和编译时性能。

可以使用 Boost.ResultOf 来确定特定调用表达式的结果类型。

头文件
#include <boost/functional/forward_adapter.hpp>
概要
namespace boost
{
    template< class Function,
        int Arity_Or_MinArity = unspecified, int MaxArity = unspecified >
    class forward_adapter;
}

符号约定

F

可能被 const 限定的函数对象类型或其引用类型

f

可转换为 F 的对象

FA

类型 forward_adapter<F>

fa

FA 的实例对象,用 f 初始化

a0...aN

传递给 fa 的参数

目标函数调用的结果类型必须是

boost::result_of<F*(TA0 [const]&...TAN [const]&])>::type

其中 TA0...TAN 表示 a0...aN 的参数类型。

表达式语义

表达式

语义

FA(f)

创建一个适配器,用 f 初始化目标函数。

FA()

创建一个适配器,尝试使用 F 的默认构造函数。

fa(a0...aN)

使用参数 a0...aN 调用 f

限制

BOOST_FUNCTIONAL_FORWARD_ADAPTER_MAX_ARITY 可以被定义来设置最大调用元数。它默认为 6。

复杂度

预处理时间:O(2^N),其中 N 是元数限制。编译时间:O(2^N),其中 N 取决于元数范围。运行时间:如果编译器内联,则为 O(0),否则为 O(1)。

说明

函数对象适配器模板,其实例可以用左值和右值参数调用。所有参数都作为引用到 const 的左值进行转发,但引用包装器会被解包,并可能产生非 const 左值。

可以通过第二个、数值型的非类型模板参数来指定元数(arity),以将转发限制为特定的元数。如果存在第三个、数值型的非类型模板参数,则第二个和第三个模板参数分别被视为最小和最大元数。指定元数有助于提高诊断消息的可读性和编译时性能。

可以使用 Boost.ResultOf 来确定特定调用表达式的结果类型。

头文件
#include <boost/functional/lightweight_forward_adapter.hpp>
概要
namespace boost
{
    template< class Function,
        int Arity_Or_MinArity = unspecified, int MaxArity = unspecified >
    struct lightweight_forward_adapter;
}

符号约定

F

可能被 const 限定的函数对象类型或其引用类型

f

可转换为 F 的对象

FA

类型 lightweight_forward_adapter<F>

fa

FA 的实例,用 f 初始化

a0...aN

传递给 fa 的参数

目标函数调用的结果类型必须是

boost::result_of<F*(TA0 [const]&...TAN [const]&])>::type

其中 TA0...TAN 表示 a0...aN 的参数类型。

表达式语义

表达式

语义

FA(f)

创建一个适配器,用 f 初始化目标函数。

FA()

创建一个适配器,尝试使用 F 的默认构造函数。

fa(a0...aN)

使用 const 参数 a0...aN 调用 f。如果 aI 是引用包装器,则会被解包。

限制

BOOST_FUNCTIONAL_LIGHTWEIGHT_FORWARD_ADAPTER_MAX_ARITY 可以被定义来设置最大调用元数。它默认为 10。

复杂度

预处理时间:O(N),其中 N 是元数限制。编译时间:O(N),其中 N 是调用的有效元数。运行时间:如果编译器内联,则为 O(0),否则为 O(1)。

由于这些实用程序是从 Boost.Fusion 函数模块中分离出来的,我想感谢 Dan Marsden 和 Joel de Guzman 让我得以参与开发这个伟大的库。

此外,我还要感谢以下参考文献的作者,感谢他们对问题及其在此实现的解决方案的深入研究。

最后但同样重要的是,我要感谢 Vesa Karnoven 和 Paul Mensonides 的 Boost Preprocessor 库。没有它,我将不得不为此编写一个外部代码生成器。

  1. The Forwarding Problem, Peter Dimov, Howard E. Hinnant, David Abrahams, 2002
  2. Boost.ResultOf, Douglas Gregor, 2004
  3. Boost.Ref, Jaakko Jarvi, Peter Dimov, Douglas Gregor, David Abrahams, 1999-2002