为什么使用 std::forward<T>而不是 static_cast&lt;T&amp;&gt; [英] Why use std::forward&lt;T&gt; instead of static_cast&lt;T&amp;&amp;&gt;

查看:81
本文介绍了为什么使用 std::forward<T>而不是 static_cast&lt;T&amp;&gt;的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当给定以下结构的代码时

When given code of the following structure

template <typename... Args>
void foo(Args&&... args) { ... }

我经常看到库代码在函数内使用 static_cast 进行参数转发.通常,这样做的理由是使用 static_cast 避免了不必要的模板实例化.

I've often seen library code use static_cast<Args&&> within the function for argument forwarding. Typically, the justification for this is that using a static_cast avoids an unnecessary template instantiation.

给定语言的参考折叠和模板推导规则.我们通过 static_cast<Args&> 获得了完美的转发,此声明的证明如下(在误差范围内,我希望答案会有所启发)

Given the language's reference collapsing and template deduction rules. We get perfect forwarding with the static_cast<Args&&>, the proof for this claim is below (within error margins, which I am hoping an answer will enlighten)

  • 当给定右值引用(或为了完整性 - 没有引用限定,如 本示例),这会崩溃以结果为右值的方式引用.使用的规则是 &&&& -> &&(上面的规则1)
  • 当给定左值引用时,这会以结果为左值的方式折叠引用.这里使用的规则是 &&& -> &(上面的规则 2)
  • When given rvalue references (or for completeness - no reference qualification as in this example), this collapses the references in such a way that the result is an rvalue. The rule used is && && -> && (rule 1 above)
  • When given lvalue references, this collapses the references in such a way that the result is an lvalue. The rule used here is & && -> & (rule 2 above)

这实质上是让 foo() 将参数转发给上述示例中的 bar().这也是您在此处使用 std::forward 时会得到的行为.

This is essentially getting foo() to forward the arguments to bar() in the example above. This is the behavior you would get when using std::forward<Args> here as well.

问题 - 为什么要在这些上下文中使用 std::forward?避免额外的实例化是否有理由打破惯例?

Question - why use std::forward in these contexts at all? Does avoiding the extra instantiation justify breaking convention?

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

Howard Hinnant's paper n2951 specified 6 constraints under which any implementation of std::forward should behave "correctly". These were

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

(1) 和 (2) 被证明可以与上面的 static_cast 一起正常工作.(3) - (6) 不适用于这里,因为在推导的上下文中调用函数时,这些都不会发生.

(1) and (2) were proven to work correctly with static_cast<Args&&> above. (3) - (6) don't apply here because when functions are called in a deduced context, none of these can occur.

注意:我个人更喜欢使用 std::forward,但我的理由纯粹是我更喜欢遵守约定.

Note: I personally prefer to use std::forward, but the justification I have is purely that I prefer to stick to convention.

推荐答案

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

forward expresses the intent and it may be safer to use than static_cast: static_cast considers conversion but some dangerous and supposedly non-intentional conversions are detected with 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
  }

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

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

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

Is the compilation time more problematic than code maintainability? Should the coder even lose its time considering minimizing "unnecessary template instantiation" at every line in the code?

当一个模板被实例化时,它的实例化会导致在其定义和声明中使用的模板的实例化.因此,例如,如果您有一个函数:

When a template is instantiated, its instantiation causes instantiations of template that are used in its definition and declaration. So that, for example if you have a function as:

  template<class T> void foo(T i){
     foo_1(i),foo_2(i),foo_3(i);
     }

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

where foo_1,foo_2,foo_3 are templates, the instantiation of foo will cause 3 instantiations. Then recursively if those functions cause the instantiation of other 3 template functions, you could get 3*3=9 instantiations for example. So you can consider this chain of instantiation as a tree where a root function instantiation can cause thousands of instantiations as an exponentially growing ripple effect. On the other hand a function like forward is a leaf in this instantiation tree. So avoiding its instantiation may only avoid 1 instantiation.

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

So, the best way to avoid template instantiation explosion is to use dynamic polymorphism for "root" classes and type of argument of "root" functions and then use static polymorphism only for time critical functions that are virtually upper in this instantiation tree.

因此,在我看来,与使用更具表现力(和更安全)的代码相比,使用 static_cast 代替 forward 来避免实例化是浪费时间.在代码架构级别更有效地管理模板实例化爆炸.

So, in my opinion using static_cast in place forward to avoid instantiations is a lost of time compared to the benefit of using a more expressive (and safer) code. Template instantiation explosion is more efficiently managed at code architecture level.

这篇关于为什么使用 std::forward<T>而不是 static_cast&lt;T&amp;&gt;的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆