版权所有 © 2007, 2008 Tobias Schwinger
根据 Boost 软件许可协议 1.0 版分发。(请参阅随附文件 LICENSE_1_0.txt 或在 https://boost.ac.cn/LICENSE_1_0.txt 复制)
boost::forward_adapter
提供了一个用于函数对象的、可重用的适配器模板。它将右值(RValues)作为常量引用转发,同时保持左值(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)以处理引用参数,并将所有其他参数限定为常量。
目标函数必须与 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;
和
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
存储其引用参数,并且编译器会阻止我们存储对具有临时生命周期的对象的引用。
重要的是要意识到,非常量性和对象是否绑定到非常量引用参数是两个不同的属性。后者是左值和右值之间的区别。名称源于赋值表达式的左侧和右侧,因此左值通常是可以赋值的,而右值是右侧表达式的临时结果。
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.
如果我们在参数上添加常量限定符,我们的函数也接受右值
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
都是常量限定的左值。我们可以使用函数重载来识别非常量左值
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 中,没有办法将右值按原样传输到函数中。另请注意,我们无法区分曾经是常量限定的左值和右值。
这就是我们使用 C++ 98 的方法尽可能接近上面描述的通用转发函数 g
的程度。有关非常详细的讨论,包括需要语言更改的解决方案,请参见 转发问题。
现在,为了实际实现它,我们需要针对 N 个参数的 2^N 个重载(每个参数都有和没有常量限定符),针对每个参数数量(即 2^(Nmax+1) - 2^Nmin)。是的,这意味着编译时复杂度为 O(2^N),但是因子很低,因此对于合理数量(< 10)的参数来说效果很好。
函数对象适配器模板,其实例可以使用左值和右值参数调用。右值参数作为引用到常量的类型化左值转发。
可以将元数作为第二个数字非类型模板参数给出,以将转发限制为特定的元数。如果存在第三个数字非类型模板参数,则第二个和第三个模板参数分别被视为最小和最大元数。指定元数有助于提高诊断消息的可读性和编译时间性能。
可以使用 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
可能是常量限定的函数对象类型或其引用类型
f
可转换为 F
的对象
FA
类型 forward_adapter<F>
fa
使用 f
初始化的 FA
的实例对象
a0
...aN
传递给 fa
的参数
目标函数调用的结果类型必须是
boost::result_of
<F*(TA0 [const]&...TAN [const]&])>::type
其中 TA0
...TAN
表示 a0
...aN
的参数类型。
表达式 |
语义 |
---|---|
|
创建一个适配器,使用 |
|
创建一个适配器,尝试使用 |
|
使用参数 |
可以定义宏 BOOST_FUNCTIONAL_FORWARD_ADAPTER_MAX_ARITY 来设置最大调用元数。它默认为 6。
预处理时间:O(2^N),其中 N 是元数限制。编译时间:O(2^N),其中 N 取决于元数范围。运行时:O(0) 如果编译器内联,否则为 O(1)。
函数对象适配器模板,其实例可以使用左值和右值参数调用。所有参数都作为引用到常量的类型化左值转发,但引用包装器除外,引用包装器会被解包并可能产生非常量左值。
可以将元数作为第二个数字非类型模板参数给出,以将转发限制为特定的元数。如果存在第三个数字非类型模板参数,则第二个和第三个模板参数分别被视为最小和最大元数。指定元数有助于提高诊断消息的可读性和编译时间性能。
可以使用 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
可能是常量限定的函数对象类型或其引用类型
f
可转换为 F
的对象
FA
类型 lightweight_forward_adapter<F>
fa
使用 f
初始化的 FA
的实例
a0
...aN
传递给 fa
的参数
目标函数调用的结果类型必须是
boost::result_of
<F*(TA0 [const]&...TAN [const]&])>::type
其中 TA0
...TAN
表示 a0
...aN
的参数类型。
表达式 |
语义 |
---|---|
|
创建一个适配器,使用 |
|
创建一个适配器,尝试使用 |
|
使用常量参数 |
可以定义宏 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 预处理器库。如果没有它,我最终将为这个库使用外部代码生成器。