Nia*_*all 32 c++ perfect-forwarding c++11 c++14
C++ 11(和C++ 14)引入了针对通用编程的其他语言结构和改进.这些功能包括:
我在浏览较早草案的的C++ 14规范(现在更新文本)和码中的示例中§20.5.1,编译时整数序列,我发现有趣和奇特.
template<class F, class Tuple, std::size_t... I>
decltype(auto) apply_impl(F&& f, Tuple&& t, index_sequence<I...>) {
return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
}
template<class F, class Tuple>
decltype(auto) apply(F&& f, Tuple&& t) {
using Indices = make_index_sequence<std::tuple_size<Tuple>::value>;
return apply_impl(std::forward<F>(f), std::forward<Tuple>(t), Indices());
}
Run Code Online (Sandbox Code Playgroud)
在线这里[intseq.general]/2.
题
f在apply_impl被转发,即为什么std::forward<F>(f)(std::get...?f(std::get...?Nia*_*all 48
TL; DR,您希望保留仿函数的值类别(r值/ l值性质),因为这会影响重载决策,特别是ref-qualified成员.
为了关注正在转发的函数的问题,我减少了样本(并使其使用C++ 11编译器进行编译);
template<class F, class... Args>
auto apply_impl(F&& func, Args&&... args) -> decltype(std::forward<F>(func)(std::forward<Args>(args)...)) {
return std::forward<F>(func)(std::forward<Args>(args)...);
}
Run Code Online (Sandbox Code Playgroud)
我们创建了第二种形式,我们将其替换为std::forward(func)just func;
template<class F, class... Args>
auto apply_impl_2(F&& func, Args&&... args) -> decltype(func(std::forward<Args>(args)...)) {
return func(std::forward<Args>(args)...);
}
Run Code Online (Sandbox Code Playgroud)
评估这种行为(使用符合编译器)的一些经验证据是评估代码示例为何如此编写的简洁起点.因此,我们将另外定义一个通用仿函数;
struct Functor1 {
int operator()(int id) const
{
std::cout << "Functor1 ... " << id << std::endl;
return id;
}
};
Run Code Online (Sandbox Code Playgroud)
初始样本
运行一些示例代码;
int main()
{
Functor1 func1;
apply_impl_2(func1, 1);
apply_impl_2(Functor1(), 2);
apply_impl(func1, 3);
apply_impl(Functor1(), 4);
}
Run Code Online (Sandbox Code Playgroud)
并且如预期的输出,独立的r值是否被用于Functor1()或1-值func进行呼叫时apply_impl和apply_impl_2重载呼叫操作被调用.它被称为r值和l值.在C++ 03下,这就是你所得到的,你不能基于对象的"r-value-ness"或"l-value-ness"来重载成员方法.
Functor1 ... 1
Functor1 ... 2
Functor1 ... 3
Functor1 ... 4
重新合格的样品
我们现在需要重载那个调用操作符来进一步拉伸它...
struct Functor2 {
int operator()(int id) const &
{
std::cout << "Functor2 &... " << id << std::endl;
return id;
}
int operator()(int id) &&
{
std::cout << "Functor2 &&... " << id << std::endl;
return id;
}
};
Run Code Online (Sandbox Code Playgroud)
我们运行另一个样本集;
int main()
{
Functor2 func2;
apply_impl_2(func2, 5);
apply_impl_2(Functor2(), 6);
apply_impl(func2, 7);
apply_impl(Functor2(), 8);
}
Run Code Online (Sandbox Code Playgroud)
而输出是;
Functor2&... 5
Functor2&... 6
Functor2&... 7
Functor2 && ... 8
讨论
在apply_impl_2(id5和6)的情况下,输出不是最初预期的.在这两种情况下,operator()都会调用l值限定(根本不调用r值).可能已经预料到,因为Functor2()使用r值来调用已经调用apply_impl_2的r值operator().的func,作为命名参数apply_impl_2,是r值参考,但由于它被命名,它是本身的左值.因此operator()(int) const&,在l值为func2参数且r值Functor2()用作参数的情况下,都会调用l值限定.
在apply_impl(id7和8)的情况下,std::forward<F>(func) 维持或保留所提供的参数的r值/ l值性质func.因此,operator()(int) const&使用l值func2作为参数调用l 值限定,并且operator()(int)&&当r值Functor2()用作参数时,r 值合格.这种行为是预期的.
std::forward通过完美转发的使用确保我们保留原始参数的r值/ l值性质func.它保留了它们的价值范畴.
它是必需的,std::forward可以而且应该不仅仅用于将参数转发给函数,而且还需要在必须保留r值/ l值性质的情况下使用参数时使用.注意; 在某些情况下,不能或不应保留r值/ l值,在这些情况下std::forward不应使用(参见下面的反向).
有许多例子突然出现,通过看似无辜地使用r值引用而无意中失去了参数的r值/ l值性质.
编写定义良好且通用的通用代码一直很难.随着r值引用的引入,特别是引用折叠,已经有可能更简洁地编写更好的通用代码,但我们需要更加了解所提供的参数的原始性质,并确保当我们在我们编写的通用代码中使用它们时,它们会被维护.
可以在此处找到完整的示例代码
std::forward<T>(t).std::forward解决了所有"普遍参考"问题?不,它没有,有些情况下不应该使用它,例如转发值不止一次.某些人可能不熟悉完美转发,那么什么是完美转发?
简而言之,完美转发是为了确保提供给函数的参数被转发(传递)到具有与最初提供的相同值类别(基本上是r值与l值)的另一个函数.它通常与模板功能一起使用,其中可能发生了参考折叠.
Scott Meyers在2013年的Going Native演示文稿中给出了以下伪代码,以解释std::forward(大约20分钟标记)的工作情况;
template <typename T>
T&& forward(T&& param) { // T&& here is formulated to disallow type deduction
if (is_lvalue_reference<T>::value) {
return param; // return type T&& collapses to T& in this case
}
else {
return move(param);
}
}
Run Code Online (Sandbox Code Playgroud)
完美的转发取决于C++ 11新增的一些基本语言结构,它们构成了我们现在在泛型编程中看到的大部分基础:
目前使用的std::forward是公式化std::forward<T>,了解std::forward作品如何帮助理解为什么会这样,并且还有助于识别rvalues的非惯用或不正确使用,参考崩溃和类似.
Thomas Becker为完美的转发问题和解决方案提供了一个很好但密集的文章.
ref-qualifiers(左值ref-qualifier &和rvalue ref-qualifier &&)类似于cv-qualifiers,因为它们(ref-qualified成员)在重载决策期间用于确定调用哪个方法.他们表现得像你期望的那样; 在&适用于左值和&&到右值.注意:与cv-qualification不同,*this仍然是l值表达式.
Yak*_*ont 13
这是一个实际的例子.
struct concat {
std::vector<int> state;
std::vector<int> const& operator()(int x)&{
state.push_back(x);
return state;
}
std::vector<int> operator()(int x)&&{
state.push_back(x);
return std::move(state);
}
std::vector<int> const& operator()()&{ return state; }
std::vector<int> operator()()&&{ return std::move(state); }
};
Run Code Online (Sandbox Code Playgroud)
此函数对象接受x并将其连接到内部std::vector.然后它返回std::vector.
如果在rvalue上下文中计算它move是临时的,否则它返回一个const&内部向量.
现在我们打电话apply:
auto result = apply( concat{}, std::make_tuple(2) );
Run Code Online (Sandbox Code Playgroud)
因为我们小心地转发了我们的函数对象,只std::vector分配了1个缓冲区.它只是搬到了result.
如果没有仔细转发,我们最终会创建一个内部std::vector,我们将其复制到result内部,然后丢弃内部std::vector.
因为operator()&&知道函数对象应该被视为要被销毁的右值,所以它可以在执行操作时从函数对象中删除内容.在operator()&无法做到这一点.
仔细使用功能对象的完美转发可实现此优化.
但请注意,此时"在野外"几乎没有使用这种技术.Rvalue限定的重载是不明确的,并且这样做更多operator().
move然而,我可以很容易地使用lambda的rvalue状态自动查看C++的未来版本,以隐含地在某些上下文中按值捕获数据.
| 归档时间: |
|
| 查看次数: |
1521 次 |
| 最近记录: |