简体   繁体   English

是否有使用prvalue的std :: forward的用例?

[英]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 std::forward的最常见用法是,完美转发转发(通用)引用,例如

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. 这里param是一个lvaluestd::forward最终将它转换为rvalue或lvalue,具体取决于与它绑定的参数。

Looking at the definition of std::forward from cppreference.com I see that there is also a rvalue overload 从cppreference.com看到std::forward定义我发现还有一个rvalue重载

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

Can anyone give me any reason why the rvalue overload? 任何人都可以给我任何理由为什么rvalue超载? 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. 如果要将rvalue传递给函数,可以按原样传递它,不需要在其上应用std::forward

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? 这与std::move不同,我在这里看到为什么还要一个rvalue重载:你可能会处理通用代码,在这些代码中你不知道你传递了什么,并且你想要无条件支持移动语义,参见eg 为什么std :: move采用通用引用? .

EDIT To clarify the question, I'm asking why overload (2) from here is necessary, and a use case for it. 编辑为了澄清这个问题,我问为什么从这里需要重载(2) ,以及它的用例。

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). 好的,因为@vsoftco要求简洁的用例,这里是一个精致的版本(使用他的想法让“my_forward”真正看到过载被调用)。

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). 我通过提供一个代码示例来解释“用例”,该代码示例没有prvalue无法编译或行为不同(无论这样做是否真的有用)。

We have 2 overloads for std::forward 我们有std::forward 2次重载

#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 我们有4个可能的用例

Use case 1 用例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 用例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 用例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 用例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 使用了重载1,没有它就会出现编译错误
  2. Overload 2 is used, without it you get compilation error 使用了重载2,没有它就会出现编译错误
  3. Overload 1 is used, wihtout it you get compilation error 使用重载1,但是没有编译错误
  4. Overload 2 is used, without it you get compilation error 使用了重载2,没有它就会出现编译错误

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. 如你所见,如果你只使用两个forward重载中的一个,你基本上导致不能编译4个案例中的2个,而如果你根本不使用forward ,你将只能编译4个案例中的3个。

This answer is for answering comment by @vsoftco 这个答案是为了回答@vsoftco的评论

@DarioOO thanks for the link. @DarioOO感谢您的链接。 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 从你的例子来看,我仍然不清楚为什么还需要为rvalues定义std :: forward

In short: 简而言之:

Because without a rvalue specialization the following code would not compile 因为没有rvalue专门化,以下代码将无法编译

#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. 您需要移动v因为Library没有构造函数接受左值,但只有rvalue引用。 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. 在上面的例子中,如果我们删除std::forward ,代码将无法编译。

So what is actually doing forward ? 那么,什么是真正在做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 代码取自我的框架(第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). 无论Impl构造函数接受rvalue还是左值(因此没有编译错误和那些都被移动), SmartPointers::resolve的返回值都被正确移动。

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 基本上你可以在任何情况下使用std::foward ,你想让代码更简单,更可读,但你必须记住2点

  • 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) 可能会导致不需要的副本(当您没有明确地将某些内容移动到需要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. 我之前盯着这个问题,读过Howard Hinnant的链接,经过一个小时的思考后无法完全理解它。 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). (编辑:得到答案太慷慨了,因为Hinnant的链接有答案。我的意思是我理解,并且能够以更简单的方式解释它,希望有人会发现有用的)。

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. 有趣的是,如果我删除std::forward ,它会打印副本。 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). (编辑:我想这会发生,因为get会返回一个左值引用到rvalue。这样的实体衰变成一个左值,但是std :: forward会把它变成一个右值,就像常用的forward一样。仍觉得不直观虽然)。

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. 假设在main的代码中, h是Hello2的一个实例。 Now, we no longer need std::forward, because the call to std::move(h).get() returns an rvalue. 现在,我们不再需要std :: forward,因为对std::move(h).get()的调用返回一个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. 现在,当我们调用func ,我们希望它与HelloHello2一起正常工作,即我们想触发一个移动。 That only happens for an rvalue of Hello if we include the outer std::forward , so we need it. 如果我们包含外部std::forward ,那只会发生对于Hello的右值,所以我们需要它。 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. 当我们将Hello2的rvalue传递给this函数时,get()的rvalue重载将返回一个rvalue double,因此std::forward实际上接受了一个rvalue。 So if it didn't, you wouldn't be able to write fully generic code as above. 因此,如果没有,您将无法像上面那样编写完全通用的代码。

Damn. 该死的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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