C ++ 11(和C ++ 14)引入了针对通用编程的其他语言结构和改进。这些功能包括:
我正在浏览draft的早期C++14 specification(现在更新了文本)和§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());
}
问题
f
中的apply_impl
函数被转发,即为什么std::forward<F>(f)(std::get...
?f(std::get...
?答案 0 :(得分:47)
TL; DR,您希望保留仿函数的value category(r值/ l值性质),因为这会影响overload resolution,尤其是ref-qualified members
为了关注正在转发的函数的问题,我已经减少了样本(并使其使用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)...);
}
我们创建了第二个表单,我们用std::forward(func)
替换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)...);
}
评估一些关于这种行为(使用符合编译器)的经验证据是评估代码示例为何如此编写的一个简洁的起点。因此,我们将另外定义一个通用函子;
struct Functor1 {
int operator()(int id) const
{
std::cout << "Functor1 ... " << id << std::endl;
return id;
}
};
初始样本
运行一些示例代码;
int main()
{
Functor1 func1;
apply_impl_2(func1, 1);
apply_impl_2(Functor1(), 2);
apply_impl(func1, 3);
apply_impl(Functor1(), 4);
}
输出与预期一致,与调用Functor1()
和func
时是否使用r值apply_impl
或l值apply_impl_2
无关调用重载的调用操作符。它被称为r值和l值。在C ++ 03下,这就是你所拥有的,你不能基于&#34; r-value-ness&#34;重载成员方法。或者&#34; l-value-ness&#34;对象。
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;
}
};
我们运行另一个样本集;
int main()
{
Functor2 func2;
apply_impl_2(func2, 5);
apply_impl_2(Functor2(), 6);
apply_impl(func2, 7);
apply_impl(Functor2(), 8);
}
输出是;
Functor2&amp; ... 5
Functor2&amp; ... 6
Functor2&amp; ... 7
Functor2&amp;&amp; ... 8
<强>讨论强>
在apply_impl_2
(id
5和6)的情况下,输出不是最初可能的预期。在这两种情况下,都会调用限定为operator()
的l值(根本不调用r值)。可能已经预期,由于Functor2()
(一个r值)用于调用apply_impl_2
,因此将调用符合条件的operator()
r值。 func
作为apply_impl_2
的命名参数,是一个r值引用,但由于它是命名的,因此它本身就是一个l值。因此,在l值operator()(int) const&
是参数和r值func2
被用作参数的情况下,调用l值限定Functor2()
。
在apply_impl
(id
7和8)的情况下,std::forward<F>(func)
维护或保留参数的r值/ l值性质提供给func
。因此,当使用r值operator()(int) const&
时,使用l值func2
作为参数调用l值限定operator()(int)&&
,并使用r值限定Functor2()
作为论点。这种行为是人们所期望的。
通过完美转发使用std::forward
可确保我们保留func
原始参数的r值/ l值性质。它保留了value category。
这是必需的,std::forward
可以而且应该不仅仅用于转发函数的参数,而且当 r-需要使用参数时必须保留值/ l值性质。注意;在某些情况下,不能或不应保留r值/ l值,在这些情况下不应使用std::forward
(参见下面的反向)。
有许多例子突然出现,通过看似无辜地使用r值引用而无意中失去了参数的r值/ l值性质。
编写定义明确且通用的通用代码一直很难。随着r值引用的引入,特别是引用折叠,已经有可能更简洁地编写更好的通用代码,但我们需要更加了解所提供的参数的原始性质,并确保当我们在我们编写的通用代码中使用它们时,它们会被维护。
可以找到完整的示例代码here
std::forward<T>(t)
。std::forward
解决所有&#34;通用参考&#34;问题?不,它没有,有些情况下should not be used,例如转发价值不止一次。有些人可能不熟悉完美转发,那么什么是完美转发?
简而言之,完美转发是为了确保提供给函数的参数被转发(传递)到具有相同值类别(基本上是r值与l值)的另一个函数originally provided。它通常用于可能发生reference collapsing的模板函数。
Scott Meyers在Going Native 2013 presentation中提供了以下伪代码来解释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);
}
}
完美的转发取决于C ++ 11新增的一些基本语言结构,这些结构构成了我们现在在泛型编程中看到的大部分基础:
std::forward
目前用于公式化std::forward<T>
,了解std::forward
的工作方式有助于理解为何如此,并有助于识别非惯用或不正确使用价值,参考崩溃和同类。
托马斯·贝克尔(Thomas Becker)在完美的转发problem和solution上提供了一篇不错但密集的文章。
ref-qualifiers(左值ref-qualifier &
和rvalue ref-qualifier &&
)类似于cv-qualifiers,因为它们(ref-qualified members)在{期间}使用{3}}确定要调用的方法。他们表现得像你期望的那样; &
适用于左值,&&
适用于右值。 注意:与cv-qualification不同,*this
仍为l值表达式。
答案 1 :(得分: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); }
};
此函数对象采用x
,并将其连接到内部std::vector
。然后它返回std::vector
。
如果在右值上下文中计算它move
为临时值,否则它会向内部向量返回const&
。
现在我们致电apply
:
auto result = apply( concat{}, std::make_tuple(2) );
因为我们小心地转发了我们的函数对象,所以只分配了1 std::vector
个缓冲区。它只是移到result
。
如果不仔细转发,我们最终会创建一个内部std::vector
,然后将其复制到result
,然后放弃内部std::vector
。
因为operator()&&
知道函数对象应该被视为要销毁的右值,所以它可以在执行操作时从函数对象中删除内容。 operator()&
无法执行此操作。
小心使用功能对象的完美转发可实现此优化。
但请注意,这种技术很少使用#34;在野外#34;在此刻。 Rvalue限定的重载是模糊的,并且这样做operator()
更多。
然而,我可以很容易地看到未来版本的C ++使用lambda的rvalue状态隐式地move
在某些上下文中的值捕获数据。