在给出以下结构的代码时
template <typename... Args>
void foo(Args&&... args) { ... }
我经常看到库代码在函数中使用static_cast<Args&&>
进行参数转发。通常,这样做的理由是使用static_cast
可以避免不必要的模板实例化。
给出该语言的参考折叠和模板推导规则。通过static_cast<Args&&>
,我们可以实现完美的转发,该声明的证据在下面(误差范围内,我希望能有所启发)
&& &&
-> &&
(上面的规则1
)& &&
-> &
(上面的规则2
)在上面的示例中,这实际上是让foo()
将参数转发到bar()
。这也是您在此处使用std::forward<Args>
时得到的行为。
问题-为什么在这些情况下完全使用std::forward
?避免额外的实例化是否符合打破常规的要求?
Howard Hinnant的论文n2951指定了6个约束,在这些约束下std::forward
的任何实现都应“正确”地表现。这些是
(1)和(2)在上述static_cast<Args&&>
下可以正常工作。 (3)-(6)在这里不适用,因为在推论上下文中调用函数时,这些都不会发生。
注意:我个人更喜欢使用std::forward
,但是我的辩解纯粹是我更愿意遵循惯例。
答案 0 :(得分:13)
Scott Meyers说std::forward
和std::move
主要是为了方便。他甚至指出std::forward
可用于执行std::forward
和std::move
的功能。
摘自“有效的现代C ++”:
项目23:了解std :: move和std :: forward
...
std::forward
的故事与std::move
的故事相似,但是std::move
无条件地将其参数强制转换为rvalue
,而std::forward
仅在某些条件下才使用。std::forward
是有条件的强制转换。仅当其参数使用rvalue
初始化时,它才会强制转换为rvalue
。
...
鉴于std::move
和std::forward
都归结为强制转换,唯一的区别是std::move
始终强制转换,而std::forward
仅有时强制转换,您可能会问我们是否可以省去std::move
,而在任何地方都可以使用std::forward
。从纯粹的技术角度来看,答案是肯定的:std::forward
可以做到。不需要std::move
。 当然,这两个功能都不是必需的,因为我们可以在任何地方编写强制类型转换,但是我希望我们同意,那样做很麻烦。
...
std::move
的吸引力在于便利性,减少出错的可能性和更高的清晰度...
对于那些感兴趣的人,在用std::forward<T>
和static_cast<T&&>
进行调用时,比较assembly中lvalue
与rvalue
的情况(无任何优化)。
答案 1 :(得分:7)
9.25+ enterd by user
23.7= enterd by user
32.95 this is your result
表达了这种意图,并且使用起来可能比forward
更安全:static_cast
考虑了转换,但是使用static_cast
会检测到一些危险的且可能是非故意的转换:
forward
错误消息的示例:compiler explorer link
此证明的适用方式:通常,证明这一点的原因是使用static_cast避免了不必要的模板实例化。
编译时间是否比代码可维护性更成问题?编码器是否应该考虑将代码中每一行的“不必要的模板实例化”减到最少,甚至会浪费时间?
实例化模板时,其实例化会导致在模板的定义和声明中使用模板的实例化。这样,例如,如果您具有以下功能:
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
}
其中 template<class T> void foo(T i){
foo_1(i),foo_2(i),foo_3(i);
}
,foo_1
,foo_2
是模板,foo_3
的实例化将导致3个实例化。然后,如果这些函数导致其他3个模板函数的实例化,则可以递归地获得3 * 3 = 9实例化。因此,您可以将此实例化链视为一棵树,其中根函数实例化可以导致成千上万个实例化,并以指数级增长的涟漪效应产生。另一方面,类似foo
的函数是此实例化树中的叶子。因此,避免实例化可能只会避免1个实例化。
因此,避免模板实例化爆炸的最佳方法是对“根”类使用动态多态性,并对“根”函数的参数类型使用动态多态性,然后仅对在该实例化树中实际上较高的时间关键型函数使用静态多态性
因此,在我看来,与使用更具表现力(和更安全)的代码相比,使用forward
代替static_cast
来避免实例化是浪费时间。在代码体系结构级别可以更有效地管理模板实例化爆炸。
答案 2 :(得分:5)
这里是我的2.5分,而不是技术上的分:今天std::forward
确实只是一个普通的static_cast<T&&>
事实,并不意味着明天也将以完全相同的方式实施。我认为委员会需要一些东西来反映std::forward
在今天取得的成就的理想行为,因此forward
并没有在任何地方转发任何东西。
从理论上讲,在std::forward
的保护下正式形成了所需的行为和期望后,没有人阻止将来的实施者提供std::forward
而不是static_cast<T&&>
而是他的特定内容自己的实现,而没有实际考虑static_cast<T&&>
,因为重要的唯一事实是std::forward
的正确用法和行为。