C++完美转发:既然可以使用const_cast(),为什么还需要forward()?
C++ perfect forwarding: why do we need forward() if we can use const_cast()?
完美转发问题的常见描述是我们最好不要使用 &
和 const&
的组合作为包装函数参数,因为在这种情况下我们不得不编写多个函数涵盖函数参数的所有组合:
template <typename T1, typename T2>
void wrapper(T1& e1, T2& e2) { func(e1, e2); }
template <typename T1, typename T2>
void wrapper(const T1& e1, T2& e2) { func(e1, e2); }
template <typename T1, typename T2>
void wrapper(T1& e1, const T2& e2) { func(e1, e2); }
template <typename T1, typename T2>
void wrapper(const T1& e1, const T2& e2) { func(e1, e2); }
下面是问题的经典解法:
template <typename T1, typename T2>
void wrapper(T1&& e1, T2&& e2) {
func(forward<T1>(e1), forward<T2>(e2));
}
template<class T>
T&& forward(typename std::remove_reference<T>::type& t) noexcept {
return static_cast<T&&>(t);
}
但是为什么我们不能使用 const_cast
来达到这个目的呢?我们可以这样写:
template <typename T1, typename T2>
void wrapper(const T1& e1, const T2& e2)
{
T1& e1_ref = const_cast<T1&>(e1);
T2& e2_ref = const_cast<T2&>(e2);
func(e1_ref, e2_ref);
}
这样,我们就不用写多个函数了,我们可以高效的处理左值和右值。那么,为什么我们真的需要一个使用引用折叠、模板参数推导和 std::forward
?
的有点棘手的解决方案
你的解决方案真的,真的很糟糕。
您修改了参数的常量性,并且不保留参数的值类别(即左值或右值)。
基本上你的包装器完全改变了参数并且总是调用相同的 func
重载而不考虑原始参数的 const 限定符和值类别。
In this way, we don't have to write multiple functions and we are able to efficiently deal with both lvalues and rvalues.
你和他们打交道,但你做错了。如果你的包装器是用右值调用的,它 "forwards" 它们作为左值。如果 func
关心左值和右值之间的区别(如果不关心,为什么还要使用完美转发呢?),如果 func
关心之间的区别,你的程序也会出现错误const 和非常量参数。
完美转发问题的常见描述是我们最好不要使用 &
和 const&
的组合作为包装函数参数,因为在这种情况下我们不得不编写多个函数涵盖函数参数的所有组合:
template <typename T1, typename T2>
void wrapper(T1& e1, T2& e2) { func(e1, e2); }
template <typename T1, typename T2>
void wrapper(const T1& e1, T2& e2) { func(e1, e2); }
template <typename T1, typename T2>
void wrapper(T1& e1, const T2& e2) { func(e1, e2); }
template <typename T1, typename T2>
void wrapper(const T1& e1, const T2& e2) { func(e1, e2); }
下面是问题的经典解法:
template <typename T1, typename T2>
void wrapper(T1&& e1, T2&& e2) {
func(forward<T1>(e1), forward<T2>(e2));
}
template<class T>
T&& forward(typename std::remove_reference<T>::type& t) noexcept {
return static_cast<T&&>(t);
}
但是为什么我们不能使用 const_cast
来达到这个目的呢?我们可以这样写:
template <typename T1, typename T2>
void wrapper(const T1& e1, const T2& e2)
{
T1& e1_ref = const_cast<T1&>(e1);
T2& e2_ref = const_cast<T2&>(e2);
func(e1_ref, e2_ref);
}
这样,我们就不用写多个函数了,我们可以高效的处理左值和右值。那么,为什么我们真的需要一个使用引用折叠、模板参数推导和 std::forward
?
你的解决方案真的,真的很糟糕。
您修改了参数的常量性,并且不保留参数的值类别(即左值或右值)。
基本上你的包装器完全改变了参数并且总是调用相同的 func
重载而不考虑原始参数的 const 限定符和值类别。
In this way, we don't have to write multiple functions and we are able to efficiently deal with both lvalues and rvalues.
你和他们打交道,但你做错了。如果你的包装器是用右值调用的,它 "forwards" 它们作为左值。如果 func
关心左值和右值之间的区别(如果不关心,为什么还要使用完美转发呢?),如果 func
关心之间的区别,你的程序也会出现错误const 和非常量参数。