简体   繁体   中英

Are there any use cases for std::forward with a prvalue?

The most common usage of std::forward is to, well, perfect forward a forwarding (universal) reference, like

template<typename T>
void f(T&& param)
{
    g(std::forward<T>(param)); // perfect forward to g
}

Here param is an lvalue , and std::forward ends up casting it to a rvalue or lvalue, depending on what the argument that bounded to it was.

Looking at the definition of std::forward from cppreference.com I see that there is also a rvalue overload

template< class T >
T&& forward( typename std::remove_reference<T>::type&& t );

Can anyone give me any reason why the rvalue overload? I cannot see any use case. If you want to pass a rvalue to a function, you can just pass it as is, no need to apply std::forward on it.

This is different from std::move , where I see why one wants also a rvalue overload: you may deal with generic code in which you don't know what you're being passed and you want unconditional support for move semantics, see eg Why does std::move take a universal reference? .

EDIT To clarify the question, I'm asking why overload (2) from here is necessary, and a use case for it.

Ok since @vsoftco asked for concise use case here's a refined version (using his idea of having "my_forward" to actually see wich overload gets called).

I interpret "use case" by providing a code sample that without prvalue not compile or behave differently (regardless of that would be really usefull or not).

We have 2 overloads for std::forward

#include <iostream>

template <class T>
inline T&& my_forward(typename std::remove_reference<T>::type& t) noexcept
{
    std::cout<<"overload 1"<<std::endl;
    return static_cast<T&&>(t);
}

template <class T>
inline T&& my_forward(typename std::remove_reference<T>::type&& t) noexcept
{
    std::cout<<"overload 2"<<std::endl;
    static_assert(!std::is_lvalue_reference<T>::value,
              "Can not forward an rvalue as an lvalue.");
    return static_cast<T&&>(t);
}

And we have 4 possible use cases

Use case 1

#include <vector>
using namespace std;

class Library
{
    vector<int> b;
public:
    // &&
    Library( vector<int>&& a):b(std::move(a)){

    }
};

int main() 
{
    vector<int> v;
    v.push_back(1);
    Library a( my_forward<vector<int>>(v)); // &
    return 0;
}

Use case 2

#include <vector>
using namespace std;

class Library
{
    vector<int> b;
public:
    // &&
    Library( vector<int>&& a):b(std::move(a)){

    }
};

int main() 
{
    vector<int> v;
    v.push_back(1);
    Library a( my_forward<vector<int>>(std::move(v))); //&&
    return 0;
}

Use case 3

#include <vector>
using namespace std;

class Library
{
    vector<int> b;
public:
    // &
    Library( vector<int> a):b(a){

    }
};

int main() 
{
    vector<int> v;
    v.push_back(1);
    Library a( my_forward<vector<int>>(v)); // &
    return 0;
}

Use case 4

#include <vector>
using namespace std;

class Library
{
    vector<int> b;
public:
    // &
    Library( vector<int> a):b(a){

    }
};

int main() 
{
    vector<int> v;
    v.push_back(1);
    Library a( my_forward<vector<int>>(std::move(v))); //&&
    return 0;
}

Here's a resume

  1. Overload 1 is used, without it you get compilation error
  2. Overload 2 is used, without it you get compilation error
  3. Overload 1 is used, wihtout it you get compilation error
  4. Overload 2 is used, without it you get compilation error

Note that if we do not use forward

Library a( std::move(v));
//and
Library a( v);

you get:

  1. Compilation error
  2. Compile
  3. Compile
  4. Compile

As you see, if you use only one of the two forward overloads, you basically cause to not compile 2 out of 4 cases, while if you do not use forward at all you would get to compile only 3 out of 4 cases.

This answer is for answering comment by @vsoftco

@DarioOO thanks for the link. Can you maybe write a succinct answer? From your example it's still not clear for me why does std::forward need to be also defined for rvalues

In short:

Because without a rvalue specialization the following code would not compile

#include <utility>
#include <vector>
using namespace std;

class Library
{
    vector<int> b;
public:
    // hi! only rvalue here :)
    Library( vector<int>&& a):b(std::move(a)){

    }
};

int main() 
{
    vector<int> v;
    v.push_back(1);
    A a( forward<vector<int>>(v));
    return 0;
}

however I can't resist to type more so here's also the not succint version of the answer.

Long version:

You need to move v because the class Library has no constructor accepting lvalue to it, but only a rvalue reference. Without perfect forwarding we would end up in a undesired behaviour:

wrapping functions would incurr high performance penality when passing heavy objects.

with move semantics we make sure that move constructor is used IF POSSIBLE. In the above example if we remove std::forward the code will not compile.

So what is actually doing forward ? moving the element without our consensus? Nope!

It is just creating a copy of the vector and moving it. How can we be sure about that? Simply try to access the element.

vector<int> v;
v.push_back(1);
A a( forward<vector<int>>(v)); //what happens here? make a copy and move
std::cout<<v[0];     // OK! std::forward just "adapted" our vector

if you instead move that element

vector<int> v;
v.push_back(1);
A a( std::move(v)); //what happens here? just moved
std::cout<<v[0];  // OUCH! out of bounds exception

So that overload is needed to make possible a implicit conversion that is still safe, but not possible without the overload.

Infact the following code will just not compile:

vector<int> v;
v.push_back(1);
A a( v); //try to copy, but not find a lvalue constructor

Real use case:

You may argue that forwarding arguments may create useless copies and hence hide a possible performance hit, yes, that's actually true, but consider real use cases:

template< typename Impl, typename... SmartPointers>
static std::shared_ptr<void> 
    instancesFactoryFunction( priv::Context * ctx){
        return std::static_pointer_cast<void>( std::make_shared<Impl>(

                std::forward< typename SmartPointers::pointerType>( 
            SmartPointers::resolve(ctx))... 
            )           );
}

Code was taken from my framework (line 80): Infectorpp 2

In that case arguments are forwarded from a function call. SmartPointers::resolve 's returned values are correctly moved regardless of the fact that constructor of Impl accept rvalue or lvalue (so no compile errors and those get moved anyway).

Basically you can use std::foward in any case in wich you want to make code simpler and more readable but you have to keep in mind 2 points

  • extra compile time (not so much in reality)
  • may cause unwanted copies (when you do not explicitly move something into something that require a rvalue)

If used with care is a powerfull tool.

I stared at this question before, read Howard Hinnant's link, couldn't fully grok it after an hour of thinking. Now I was looking and got the answer in five minutes. (Edit: got the answer is too generous, as Hinnant's link had the answer. I meant that I understood, and was able to explain it in a simpler way, which hopefully someone will find helpful).

Basically, this allows you to be generic in certain kinds of situations depending on the typed that's passed in. Consider this code:

#include <utility>
#include <vector>
#include <iostream>
using namespace std;

class GoodBye
{
  double b;
 public:
  GoodBye( double&& a):b(std::move(a)){ std::cerr << "move"; }
  GoodBye( const double& a):b(a){ std::cerr << "copy"; }
};

struct Hello {
  double m_x;

  double & get()  { return m_x; }
};

int main()
{
  Hello h;
  GoodBye a(std::forward<double>(std::move(h).get()));
  return 0;
}

This code prints "move". What's interesting is that if I remove the std::forward , it prints copy. This, for me, is hard to wrap my mind around, but let's accept it and move on. (Edit: I suppose this happens because get will return a lvalue reference to an rvalue. Such an entity decays into an lvalue, but std::forward will cast it into an rvalue, just as in the common use of forward. Still feels unintuitive though).

Now, let's imagine another class:

struct Hello2 {
  double m_x;

  double & get() & { return m_x; }
  double && get() && { return std::move(m_x); }
};

Suppose in the code in main , h was an instance of Hello2. Now, we no longer need std::forward, because the call to std::move(h).get() returns an rvalue. However, suppose the code is generic:

template <class T>
void func(T && h) {
  GoodBye a(std::forward<double>(std::forward<T>(h).get()));
}

Now when we call func , we'd like it to work properly with both Hello and Hello2 , ie we'd like to trigger a move. That only happens for an rvalue of Hello if we include the outer std::forward , so we need it. But... We got to the punchline. When we pass an rvalue of Hello2 to this function, the rvalue overload of get() will already return an rvalue double, so std::forward is actually accepting an rvalue. So if it didn't, you wouldn't be able to write fully generic code as above.

Damn.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM