Common descriptions of the perfect forwarding problem say that we better shouldn't use the combinations of &
and const&
as wrapper function arguments, because in such case we are obliged to write multiple functions covering all combinations of the function arguments:
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); }
Here is the classic solution of the problem:
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);
}
But why can't we use const_cast
for this purpose? We could write something like:
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);
}
In this way, we don't have to write multiple functions and we are able to efficiently deal with both lvalues and rvalues. So why do we really need a somewhat tricky solution using reference collapsing, template arguments deduction and std::forward
?