C++ 为什么要使用完全转发的值(函子)?

C++ 为什么要使用完全转发的值(函子)?,c++,c++11,perfect-forwarding,c++14,C++,C++11,Perfect Forwarding,C++14,C++11(和C++14)引入了针对泛型编程的其他语言构造和改进。这些特点包括: R值参考 参考折叠 完美转发 移动语义、可变模板等 我浏览了一个早期版本(现在有更新的文本)和§20.5.1编译时整数序列示例中的代码,我发现这些代码既有趣又奇特 template<class F, class Tuple, std::size_t... I> decltype(auto) apply_impl(F&& f, Tuple&& t, index_sequ

C++11(和C++14)引入了针对泛型编程的其他语言构造和改进。这些特点包括:

  • R值参考
  • 参考折叠
  • 完美转发
  • 移动语义、可变模板等
我浏览了一个早期版本(现在有更新的文本)和§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());
}
模板
decltype(自动)apply\u impl(F&&F,元组&&t,索引\u序列){
返回std::forward(f)(std::get(std::forward(t))…);
}
模板
decltype(自动)应用(F&&F、元组&&t){
使用索引=生成索引序列;
返回apply_impl(std::forward(f)、std::forward(t)、index());
}
在线这里

问题

  • 为什么转发
    apply\u impl
    中的函数
    f
    ,即为什么转发
    std::forward(f)(std::get…
  • 为什么不将函数作为
    f(std::get…
    )应用呢
简而言之。。。 <>太长了,读不下去了,你要保存函子的(r值/L值性质),因为这会影响,尤其是

函数定义约简 为了关注函数被转发的问题,我将示例(并使用C++11编译器编译)缩减为

样本评估 评估这种行为的一些经验证据(使用符合标准的编译器)是评估代码示例为何如此编写的一个整洁的起点

struct Functor1 {
  int operator()(int id) const
  {
    std::cout << "Functor1 ... " << id << std::endl;
    return id;
  }
};
输出与预期一致,与是否使用r值无关或l值
func
在调用
apply\u impl
apply\u impl\u 2
时,会调用重载的调用运算符。它同时对r值和l值进行调用。在C++03下,这就是您所能得到的,您不能基于对象的“r值属性”或“l值属性”重载成员方法

函数1…1
函数1…2
函数1…3
函子1…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;
  }
};
输出为

Functor2&…5
Functor2&…6
Functor2&…7
函子2&…8

讨论

apply\u impl\u 2
id
5和6)的情况下,输出与最初预期的不一样。在这两种情况下,调用l值限定的
运算符()
(根本不调用r值)。从
Functor2()开始,可能预期
是一个r值,用于调用
apply\u impl\u 2
将调用的r值限定的
运算符()
func
作为
apply\u impl\u 2
的命名参数,是一个r值引用,但由于它被命名,它本身就是一个l值。因此,l值限定的
运算符()(int)const&
在l-value
func2
作为参数和r-value
Functor2()
作为参数的情况下都被调用

apply_impl
id
7和8)的情况下,
std::forward(func)
维护或保留为
func
提供的参数的r值/l值性质。因此,l值限定为
运算符()(int)当r值
Functor2()
用作参数时,调用const&
,l值
func2
用作参数,r值限定
运算符()(int)&
用作参数。这种行为是预期的

结论 通过完美转发使用
std::forward
,确保我们保留了
func
原始参数的r值/l值性质。它保留了它们的属性。

它是必需的,
std::forward
不仅可以而且应该用于将参数转发给函数,而且在必须保留r-value/l-value性质的情况下需要使用参数时也可以使用。注意:在这些情况下,r-value/l-value不能或不应该被保留不应使用(见下文相反)

出现了许多例子,它们通过看似无辜地使用r值引用,无意中失去了参数的r值/l值性质

编写定义良好且合理的通用代码一直很困难。随着r值引用的引入,尤其是引用崩溃,编写更好的通用代码变得可能,更简洁,但我们需要更加了解所提供参数的原始性质,并确保它们是当我们在我们编写的通用代码中使用它们时,会保留它们

可以找到完整的示例代码

推论与逆
  • 这个问题的一个推论是:假设引用在模板化函数中崩溃,参数的r值/l值性质是如何保持的?答案是-use
    std::forward(t)
  • Converse;
    std::forward
    是否解决了所有“通用参考”问题?不,它不会解决,在某些情况下,它会解决问题,例如多次转发值

完美转发的简要背景 完美转发对某些人来说可能并不熟悉,那么什么是完美转发呢

简言之,完美转发是为了确保提供给函数的参数被转发(传递)给另一个具有相同值类别(基本上是r值与l值)的函数。它通常与可能发生的模板函数一起使用

Scott Meyers在其著作中给出了以下伪代码,以解释
std::forward
(大约在20分钟时)的工作原理

模板
T&前进
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);
}
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);
}
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);
  }
}
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); }
};
auto result = apply( concat{}, std::make_tuple(2) );