使用std :: forward与auto&&正确的做法

时间:2013-07-02 15:03:00

标签: c++ c++11

尝试了解使用带有std::forward变量的auto&&是否是传递这些变量以允许移动的正确方法。

假设有一个功能:

void moveWidget(Widget&& w);

调用者 - 两个引用rvalue和lvalue的变量:

Widget w;
auto&& uniRefLV = w;            // lvalue initialiser, 
                                // uniRefLV's type is Widget&

auto&& uniRefRV = std::move(w); // rvalue initialiser, 
                                // uniRefRV's type is Widget&&

我们知道auto&&类型的变量是通用引用,因为存在类型推断。这意味着uniRefRVuniRefLV都是通用引用

在我的示例中,很明显uniRefRV rvalue uniRefLV lvalue ,但从概念上讲,它们都是通用引用< / em>如果定义不同,则可以表示 rvalue lvalue

现在,我想调用moveWidget()并完善这些通用引用类型。该指南(由Scott Meyers撰写)说:

  

通过std::move通用参考通过std::forward传递和返回右值参考

除非我完全误解了指南,否则使用std::forward似乎合乎逻辑。但是,让我们考虑所有可能的选择:

// (1) std::move:
moveWidget(std::move(uniRefLV)); // Compiles and looks fine
                                 // but violates the guideline?
                                 // (unconditionally casts lvalue to rvalue)

moveWidget(std::move(uniRefRV)); // Same as above - but not an issue here
                                 // as we cast rvalue to rvalue

// (2) std::forward with Widget:
moveWidget(std::forward<Widget>(uniRefLV)); // Compiles, follows the guideline
                                            // but doesn't look right - what if
                                            // we didn't know Widget's type?

moveWidget(std::forward<Widget>(uniRefRV)); // Same as above

// (3) std::forward with decltype:
moveWidget(std::forward<decltype(uniRefLV)>(uniRefLV)); // Fails to compile! (VC10)
                                                        // follows the guideline
                                                        // has nice and short syntax :)

moveWidget(std::forward<decltype(uniRefRV)>(uniRefRV)); // Compiles fine

您认为我们应该同等对待引用uniRefLVuniRefRV以及我们应该使用哪三个选项进行完美转发?

4 个答案:

答案 0 :(得分:11)

您误解了指南。或者至少从字面上看它。

我认为这里有三个重要的事情需要实现。

首先,这里所有类型都是已知的。对于通用引用的建议主要适用于带有模板的通用代码,如果某些内容是或者采用左值引用或右值引用,则根本不知道。

其次,该函数采用右值引用:您必须传递右值。期。这里没有选择。

逻辑上的结论是你想要传递一个通用引用:无论你传递的是一个右值,它都不能是一个左值。通用引用可以是左值(如果它们被实例化为左值引用)。传递一个通用引用意味着“我不知道这是什么样的引用,我可以将它作为rvalue或左值传递,所以我正在传递它,就像我得到它一样”。这个问题的情况更像是“我确切地知道我必须通过什么,所以这就是我要传递的东西”。

答案 1 :(得分:5)

让我们假设案件并不像看起来那么简单。而不是giveMeInt我们有类似的东西:

template <typename T>
typename complex_computation<T>::type giveMeSomething(T t);

而不是moveMe,你有一些实际上需要通用引用的东西,因此不需要无条件的std::move

template <typename T>
void wantForwarded(T&&);

现在你实际上需要完美转发。

auto&& uniRef = giveMeSomething(iDontKnowTheTypeOfThis);
wantForwarded(std::forward<decltype(uniRef)>(uniRef);

答案 2 :(得分:4)

我觉得你有点混淆了。 uniRefLVuniRefRV都不是“通用引用”。它们只是变量,两者都有一定的类型。这两种类型恰好都是引用类型,但这并不重要。

要调用moveWidget,您必须在两种情况下使用std::move,并且在这两种情况下,语义都是在移动后,您的原始w已被移除,因此不再是一个明确的状态。 (你可以用它做的最好的事情是破坏它或重新分配它。)

现在,相比之下,真正的通用引用模板参数,它必须始终是规范形式:

template <typename T> void foo(T &&);
//                            ^^^^^^

foo(bar());
foo(x);
foo(std::move(y));

即,

  • 模板是功能模板,
  • 模板参数T作为函数参数T &&
  • 出现
  • 实际专业化的模板参数是推导

满足这些条件时,T &&是通用引用,您可以通过std::forward<T>传递它,这会保留正确的引用类型。

答案 3 :(得分:0)

<强>(1).A  它没有违反指南。你明确地说“我不再需要那个左值,所以接受它”这基本上就是std :: move的用途。如果你在完成std :: move之后继续使用uniRefLV - 这是违反准则的。价值消失了(可能),你把它传了过来。这就是 Scott 所说的。

<强>(1).B  这是合法的。虽然那里不需要std :: move,但它确实简化了源代码的阅读。

(2),(3)  我们不必为std :: forward提供模板参数。它应由编译器推导出来!至少这就是我的理解。而且我没有看到手动完成这一点。转发只是从类型定义中删除引用(&amp;&amp;和&amp;)。这就是为什么它被称为转发并用于数据转发。当你有功能:     模板     void foo(T&amp; t);

可以将其实例化为:

void foo(SomeType & t);
void foo(SomeType && t);
void foo(const SomeType & t);
void foo(const SomeType && t);
void foo(volatile SomeType & t);
void foo(volatile SomeType && t);
void foo(const volatile SomeType & t);
void foo(const volatile SomeType && t);

例如:

Widget a;
foo(a);

将实例化:

void foo(Widget & a);

void foo(Widget a);

所以这些引用都被std :: forward删除了。只有*modificator* SomeType t被呈现给传递给它的foo()内的任何函数。