为什么使用std :: forward <T>而不是static_cast <T &&>

Cur*_*ous 23 c++ templates forwarding rvalue-reference c++17

当给出以下结构的代码时

template <typename... Args>
void foo(Args&&... args) { ... }
Run Code Online (Sandbox Code Playgroud)

我经常static_cast<Args&&>在函数中看到库代码用于参数转发.通常,对此的理由是使用static_cast避免不必要的模板实例化.

鉴于语言的参考折叠和模板扣除规则.我们得到了完美的转发,static_cast<Args&&>这个声明的证据如下(在误差范围内,我希望答案会启发)

  • 当给出rvalue引用时(或者为了完整性 - 没有像本示例中那样的引用限定条件),这会以这样的方式折叠引用,即结果是rvalue.使用的规则是&& &&- > &&(1上面的规则)
  • 当给定左值引用时,这会以这样的方式折叠引用,即结果是左值.这里使用的规则是& &&- > &(2上面的规则)

这基本上foo()bar()在上面的例子中转发参数.这也是你在std::forward<Args>这里使用时会得到的行为.


问题 -为什么要std::forward在这些背景下使用?避免额外的实例化是否有理由违反惯例?

Howard Hinnant的论文n2951规定了6个约束,在这些约束条件下,任何实现都std::forward应该"正确".这些曾经是

  1. 应该将左值作为左值转发
  2. 应该将右值作为右值转发
  3. 不应该将右值作为左值转发
  4. 应该将更少的cv限定表达式转发给更多cv限定的表达式
  5. 应该将派生类型的表达式转发为可访问的,明确的基类型
  6. 不应转发任意类型的转换

(1)和(2)证明与static_cast<Args&&>上述方法一起正常工作.(3) - (6)这里不适用,因为在推导的上下文中调用函数时,这些都不会发生.


注意:我个人更喜欢使用std::forward,但我的理由纯粹是我更喜欢坚持惯例.

P.W*_*P.W 13

斯科特迈尔斯说,std::forwardstd::move主要是为了方便.他甚至指出,std::forward可用于同时执行的功能std::forwardstd::move.
"Effective Modern C++"的一些摘录:

第23项:理解std :: move和std :: forward
......
故事std::forward类似于for std::move,但是std::move无条件地将其论证转换为a rvalue,std::forward仅在某些条件下才会这样做.std::forward是条件演员.rvalue只有当它的参数用a初始化时才转换为rvalue.
......
考虑到两者std::movestd::forward归结为演员阵容,唯一的区别在于std::move总是演员阵容,而std::forward有时候只是,你可能会问我们是否可以放弃std::move并且只是std::forward在任何地方使用.从纯粹的技术角度来看,答案是肯定的:std::forward可以做到这一切.std::move没有必要.当然,这两种功能都不是必需的,因为我们可以在任何地方写出演员,但我希望我们同意那将是,令人讨厌的.
...
std::move的吸引力是方便,错误的可能性降低,清晰度更高......

对于那些有兴趣,比较std::forward<T>VS static_cast<T&&>装配时,一个名为(不使用任何优化)lvaluervalue.

  • ......总之,这意味着std :: move比std :: forward需要更少的输入,并且它使我们省去了传递一个类型参数的麻烦,该参数编码我们传递的参数是一个右值.它还消除了我们传递错误类型的可能性(例如,std :: string&,这将导致数据成员被复制构造而不是移动构造).更重要的是,std :: move的使用将无条件转换传递给rvalue,而std :: forward的使用表示仅对引用了rvalues的rvalue进行转换. (3认同)
  • 预计此评论会有所不同.:-)我不认为斯科特迈耶斯意味着我们可以用前锋取代移动.他举一个例子来说明在前进方面实施移动的项目."std :: move只需要一个函数参数(rhs.s),而std :: forward需要一个函数参数(rhs.s)和一个模板类型参数(std :: string).然后注意我们传递的类型to std :: forward应该是一个非引用,因为这是编码的约定,传递的参数是一个rvalue(参见条款28).contd ... (2认同)

Oli*_*liv 7

forward表达意图并且使用起来可能比使用更安全static_cast:static_cast考虑转换,但检测到一些危险的和所谓的非故意转换forward:

struct A{
   A(int);
   };

template<class Arg1,class Arg2>
Arg1&& f(Arg1&& a1,Arg2&& a2){
   return static_cast<Arg1&&>(a2); //  typing error: a1=>a2
   }

template<class Arg1,class Arg2>
Arg1&& g(Arg1&& a1,Arg2&& a2){
   return forward<Arg1>(a2); //  typing error: a1=>a2
   }

void test(const A a,int i){
   const A& x = f(a,i);//dangling reference
   const A& y = g(a,i);//compilation error
  }
Run Code Online (Sandbox Code Playgroud)

错误消息示例:编译器资源管理器链接


如何应用这种理由:通常,这样做的理由是使用static_cast可以避免不必要的模板实例化.

编译时间比代码可维护性更有问题吗?编码员是否应该考虑尽量减少代码中每一行的"不必要的模板实例化"?

实例化模板时,其实例化会导致在其定义和声明中使用的模板实例化.所以,例如,如果你有一个函数:

  template<class T> void foo(T i){
     foo_1(i),foo_2(i),foo_3(i);
     }
Run Code Online (Sandbox Code Playgroud)

其中foo_1,foo_2,foo_3是模板,的实例化foo将导致3个实例.然后递归地,如果这些函数导致其他3个模板函数的实例化,例如,您可以获得3*3 = 9个实例化.因此,您可以将此实例化链视为一个树,其中根函数实例化可以导致数千个实例化作为指数增长的涟漪效应.另一方面,函数就像forward是这个实例化树中的叶子.因此,避免其实例化可能只能避免1次实例化.

因此,避免模板实例化爆炸的最佳方法是对"root"类和"root"函数的参数类型使用动态多态,然后仅对在此实例化树中实际上位的时间关键函数使用静态多态.

所以,在我看来,与使用更具表现力(和更安全)的代码相比,使用static_cast到位forward来避免实例化是一种时间的浪费.在代码体系结构级别更有效地管理模板实例化爆炸.

  • @YSC:似乎是https://en.wikipedia.org/wiki/Indentation_style#Ratliff_style (3认同)
  • 这支撑式怎么称呼? (2认同)

Fer*_*eak 5

这是我的2.5,不是那么技术上的分数:今天std::forward确实只是一个普通的旧static_cast<T&&>事实并不意味着明天它也将以完全相同的方式实施.我认为委员会需要一些东西来反映std::forward今天所取得的成果所期望的行为,而forward这种行为并没有随处出现.

随着具有所要求的行为和期望的保护伞下形式化std::forward,只是从理论上讲,没有人阻碍未来的实施者提供std::forward的不是static_cast<T&&>,但是具体自己实现的东西,实际上并没有考虑到static_cast<T&&>,因为唯一重要的事实是正确的用法和行为std::forward.

  • 我真诚地怀疑“forward”是否会做其他事情。想想有多少代码会被破坏...... (2认同)