What Are the Main Purposes of Using Std::Forward and Which Problems It Solves

Why is std::forward necessary with forwarding references [duplicate]

You really don't want your parameters to be automatically moved to the functions called. Consider this function:

template <typename T>
void foo(T&& x) {
bar(x);
baz(x);
global::y = std::forward<T>(x);
}

Now you really don't want an automatic move to bar and an empty parameter to baz.

The current rules of requiring you to specify if and when to move or forward a parameter are not accidental.

Reason for using std::forward before indexing operator in Effective Modern C++ example

It depends on how the Container is implemented. If it has two reference-qualified operator[] for lvalue and rvalue like

T& operator[] (std::size_t) &; // used when called on lvalue
T operator[] (std::size_t) &&; // used when called on rvalue

Then

template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i)
{
authenticateUser();
return std::forward<Container>(c)[i]; // call the 1st overload when lvalue passed; return type is T&
// call the 2nd overload when rvalue passed; return type is T
}

It might cause trouble without forwarding reference.

template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i)
{
authenticateUser();
return c[i]; // always call the 1st overload; return type is T&
}

Then

const T& rt = authAndAccess(Container{1, 5, 9}, 0);
// dangerous; rt is dangling

BTW this doesn't work for std::vector because it doesn't have reference-qualified operator[] overloads.

Role of std::forward

The problem is that in

template <typename T>
void pass_through(T&& param) {
overloaded_function(param);
}

param is a named variable. Since it has a name, it is an lvalue, even though it is a reference to an rvalue. Since it is an lvalue, you call the lvalue function.

You need std::forward to "re-cast" it back into a rvalue if it came in as an rvalue.


Also note that T is not string&&. Since you passed a string&&, T&& deduces T to string and the && is kept to make it an rvalue reference.

Meaning of std::forwardstd::decay_tF(f)

,The code you have shared is toxic.

template <typename F>
class FixPoint : private F
{
public:
explicit constexpr FixPoint(F&& f)

what this means is that we expect F to be a value type (because inheriting from a reference isn't possible). In addition, we will only accept rvalues -- we will only move from another F.

  : F{std::forward<F>(f)}
{}

this std::forward<F> is pointless; this indicates that the person writing this code thinks they are perfect forwarding: they are not. The only legal types F are value types (not references), and if F is a value type F&& is always an rvalue reference, and thus std::forward<F> is always equivalent to std::move.

There are cases where you want to

template<class X>
struct wrap1 {
X&& x;
wrap1(X&& xin):x(std::forward<X>(xin)){}
};

or even

template<class X>
struct wrap2 {
X x;
wrap2(X&& xin):x(std::forward<X>(xin)){}
};

so the above code is similar to some use cases, but it isn't one of those use cases. (The difference here is that X or X&& is the type of a member, not a base class; base classes cannot be references).

The use for wrap2 is when you want to "lifetime extend" rvalues, and simply take references to lvalues. The use for wrap1 is when you want to continue the perfect forwarding of some expression (wrap1 style objects are generally unsafe to keep around for more than a single line of code; wrap2 are safe so long as they don't outlive any lvalue passed to them).



template <typename F>
inline constexpr decltype(auto)
makeFixPoint(F&& f) noexcept
{
return FixPoint<std::decay_t<F>>{std::forward<std::decay_t<F>>(f)};
}

Ok more red flags here. inline constexpr is a sign of nonsense; constexpr functions are always inline. There could be some compilers who treat the extra inline as meaning something, so not a guarantee of a problem.

return FixPoint<std::decay_t<F>>{std::forward<std::decay_t<F>>(f)};

std::forward is a conditional move. It moves if there is an rvalue or value type passed to it. decay is guaranteed to return a value type. So we just threw out the conditional part of the operation, and made it into a raw move.

return FixPoint<std::decay_t<F>>{std::move(f)};

this is a simpler one that has the same meaning.

At this point you'll probably see the danger:

template <typename F>
constexpr decltype(auto)
makeFixPoint(F&& f) noexcept
{
return FixPoint<std::decay_t<F>>{std::move(f)};
}

we are unconditionally moving from the makeFixPoint argument. There is nothing about the name makeFixPoint that says "I move from the argument", and it accept forwarding references; so it will silently consume an lvalue and move-from it.

This is very rude.


So a sensible version of the code is:

template <typename F>
class FixPoint : private F
{
public:
template<class U,
class dU = std::decay_t<U>,
std::enable_if_t<!std::is_same_v<dU, FixPoint> && std::is_same_v<dU, F>, bool> = true
>
explicit constexpr FixPoint(U&& u) noexcept
: F{std::forward<U>(u)}
{}

[SNIP]

namespace
{
template <typename F>
constexpr FixPoint<std::decay_t<F>>
makeFixPoint(F&& f) noexcept
{
return FixPoint<std::decay_t<F>>{std::forward<F>(f)};
}
} // namespace

that cleans things up a bit.

When to use std::forward to forward arguments?

Use it like your first example:

template <typename T> void f(T && x)
{
g(std::forward<T>(x));
}

template <typename ...Args> void f(Args && ...args)
{
g(std::forward<Args>(args)...);
}

That's because of the reference collapsing rules: If T = U&, then T&& = U&, but if T = U&&, then T&& = U&&, so you always end up with the correct type inside the function body. Finally, you need forward to turn the lvalue-turned x (because it has a name now!) back into an rvalue reference if it was one initially.

You should not forward something more than once however, because that usually does not make sense: Forwarding means that you're potentially moving the argument all the way through to the final caller, and once it's moved it's gone, so you cannot then use it again (in the way you probably meant to).

Use of std::forward in c++

As the page you linked poses it:

This is a helper function to allow perfect forwarding of arguments
taken as rvalue references to deduced types, preserving any potential
move semantics involved.

When you have a named value, as in

void f1(int& namedValue){
...
}

or in

void f2(int&& namedValue){
...
}

it evaluates, no matter what, to an lvalue.

One more step. Suppose you have a template function

template <typename T>
void f(T&& namedValue){
...
}

such function can either be called with an lvalue or with an rvalue; however, no matter what, namedValue evaluates to an lvalue.

Now suppose you have two overloads of an helper function

void helper(int& i){
...
}
void helper(int&& i){
...
}

calling helper from inside f

template <typename T>
void f(T&& namedValue){
helper(namedValue);
}

will invariably call the first overload for helper, since namedValue is, well, a named value which, naturally, evaluates to an lvalue.

In order to get the second version called when appropriate (i.e. when f has been invoked with a rvalue parameter), you write

template <typename T>
void f(T&& namedValue){
helper( std::forward<T>(namedValue) );
}

All of this is expressed much concisely in the documentation by the following

The need for this function stems from the fact that all named values
(such as function parameters) always evaluate as lvalues (even those
declared as rvalue references), and this poses difficulties in
preserving potential move semantics on template functions that forward
arguments to other functions.



Related Topics



Leave a reply



Submit