You should use std::forward to keep the category of argument (s) values up to fn() . Since the arguments have a name inside fn , they are lvalues, and without std::forward they will always be passed as such to std::forward_as_tuple .
The difference can be demonstrated using the following example :
template<typename T> void bar2(T&& t) { std::cout << __PRETTY_FUNCTION__ << ' ' << std::is_rvalue_reference<decltype(t)>::value << '\n'; } template<typename T> void bar1(T&& t) { std::cout << __PRETTY_FUNCTION__ << ' ' << std::is_rvalue_reference<decltype(t)>::value << '\n'; bar2(std::forward<T>(t)); bar2(t); }
bar1 always passes arguments to bar2 , once with std::forward and once without it. Now call them with the argument lvalue and rvalue.
foo f; bar1(f); std::cout << "--------\n"; bar1(foo{});
Conclusion:
void bar1(T&&) [with T = foo&] 0 void bar2(T&&) [with T = foo&] 0 void bar2(T&&) [with T = foo&] 0 -------- void bar1(T&&) [with T = foo] 1 void bar2(T&&) [with T = foo] 1 void bar2(T&&) [with T = foo&] 0
As you can see from the output, in both cases, without using std::forward argument is passed as lvalue to bar2 .
source share