使用std :: forward的主要目的是什么,它可以解决哪些问题?

问题描述 投票:438回答:6

[在完美转发中,std::forward用于将命名的右值引用t1t2转换为未命名的右值引用。这样做的目的是什么?如果将innert1保留为左值,这将如何影响调用的函数t2

template <typename T1, typename T2>
void outer(T1&& t1, T2&& t2) 
{
    inner(std::forward<T1>(t1), std::forward<T2>(t2));
}
c++ c++11 rvalue-reference c++-faq perfect-forwarding
6个回答
789
投票

您必须了解转发问题。您可以read the entire problem in detail,但我总结一下。

[基本上,给定表达式E(a, b, ... , c),我们希望表达式f(a, b, ... , c)是等效的。在C ++ 03中,这是不可能的。尝试了很多,但都在某些方面失败了。


最简单的是使用左值引用:

template <typename A, typename B, typename C>
void f(A& a, B& b, C& c)
{
    E(a, b, c);
}

但是这无法处理临时值:f(1, 2, 3);,因为这些值不能绑定到左值引用。

下一次尝试可能是:

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
    E(a, b, c);
}

可以解决上述问题,但会出现触发器。现在,它无法允许E具有非常量参数:

int i = 1, j = 2, k = 3;
void E(int&, int&, int&); f(i, j, k); // oops! E cannot modify these

第三次尝试接受const引用,但是const_cast离开了const

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
    E(const_cast<A&>(a), const_cast<B&>(b), const_cast<C&>(c));
}

这将接受所有值,可以传递所有值,但可能导致未定义的行为:

const int i = 1, j = 2, k = 3;
E(int&, int&, int&); f(i, j, k); // ouch! E can modify a const object!

最终解决方案正确地处理了所有事情……以无法维护为代价。您可以使用const和非const的all组合提供f的重载:

template <typename A, typename B, typename C>
void f(A& a, B& b, C& c);

template <typename A, typename B, typename C>
void f(const A& a, B& b, C& c);

template <typename A, typename B, typename C>
void f(A& a, const B& b, C& c);

template <typename A, typename B, typename C>
void f(A& a, B& b, const C& c);

template <typename A, typename B, typename C>
void f(const A& a, const B& b, C& c);

template <typename A, typename B, typename C>
void f(const A& a, B& b, const C& c);

template <typename A, typename B, typename C>
void f(A& a, const B& b, const C& c);

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c);

N个参数需要2 N个组合,这是一场噩梦。我们希望自动执行此操作。

((这实际上是使编译器在C ++ 11中为我们完成的工作。)


在C ++ 11中,我们有机会解决此问题。 One solution modifies template deduction rules on existing types, but this potentially breaks a great deal of code.因此,我们必须找到另一种方法。

解决方案是改为使用新添加的rvalue-references;我们可以在推导右值引用类型并创建任何所需结果时引入新规则。毕竟,我们现在不可能破坏代码。

[如果给出对引用的引用(注意引用是一个包含T&T&&的涵盖性术语),我们将使用以下规则找出结果类型:

“ [给定]作为类型T的引用的类型TR,尝试创建类型“对cv TR的左值引用”会创建类型“对T的左值引用”,而尝试创建对类型“ rvalue的引用”引用cv TR”将创建类型TR。“

或以表格形式:

TR   R

T&   &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&   && -> T&  // rvalue reference to cv TR -> TR (lvalue reference to T)
T&&  &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&&  && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)

[接下来,使用模板参数推导:如果参数是左值A,则为模板参数提供对A的左值引用。否则,我们可以正常推断。这给出了所谓的通用参考(术语forwarding reference现在是正式名称)。

为什么这有用?因为结合起来,我们可以保持跟踪类型的值类别的能力:如果是左值,则我们有一个左值引用参数,否则就有一个右值引用参数。

使用代码:

template <typename T>
void deduce(T&& x); 

int i;
deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&)
deduce(1); // deduce<int>(int&&)

最后一件事是“转发”变量的值类别。请记住,一旦在函数内部,该参数就可以作为左值传递给任何东西:

void foo(int&);

template <typename T>
void deduce(T&& x)
{
    foo(x); // fine, foo can refer to x
}

deduce(1); // okay, foo operates on x which has a value of 1

这不好。 E需要获得与我们得到的相同的价值类别!解决方法是这样的:

static_cast<T&&>(x);

这是做什么的?假设我们在deduce函数内部,并且已经传递了一个左值。这意味着TA&,因此静态类型转换的目标类型是A& &&,或仅是A&。由于x已经是A&,因此我们什么也不做,只剩下一个左值引用。

当我们传递一个右值时,TA,因此静态类型转换的目标类型为A&&。强制转换产生右值表达式无法再传递给左值引用。我们维护了参数的值类别。

将它们放在一起可以使我们“完美转发”:

template <typename A>
void f(A&& a)
{
    E(static_cast<A&&>(a)); 
}

[f收到一个左值时,E得到一个左值。当f收到一个右值时,E得到一个右值。完美。


当然,我们想摆脱丑陋的地方。 static_cast<T&&>晦涩难懂,难以记住;让我们创建一个称为forward的实用程序函数,该函数执行相同的操作:

std::forward<A>(a);
// is the same as
static_cast<A&&>(a);

61
投票

我认为有一个实现std :: forward的概念代码可以增加讨论的范围。这是来自Scott Meyers演讲An Effective C++11/14 Sampler

的幻灯片

“实现std]的概念代码

代码中的功能movestd::move。在该演讲的前面有一个(有效的)实现。我在文件move.h中找到了actual implementation of std::forward in libstdc++,但这完全没有启发性。

从用户的角度来看,其含义是std::forward是有条件强制转换为右值。如果我编写的函数期望参数中包含左值或右值,并且仅当将其作为右值传递时,希望将其作为右值传递给另一个函数,则该功能很有用。如果我没有将参数包装在std :: forward中,它将始终作为常规引用传递。

#include <iostream>
#include <string>
#include <utility>

void overloaded_function(std::string& param) {
  std::cout << "std::string& version" << std::endl;
}
void overloaded_function(std::string&& param) {
  std::cout << "std::string&& version" << std::endl;
}

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

int main() {
  std::string pes;
  pass_through(pes);
  pass_through(std::move(pes));
}

当然,它打印

std::string& version
std::string&& version

该代码基于前面提到的一个示例。从大约15:00开始滑动10。


34
投票

在完美转发中,std :: forward用于将命名的右值引用t1和t2转换为未命名的右值引用。这样做的目的是什么?如果我们将t1和t2保留为左值,对被调用函数的内部有什么影响?


11
投票

如果将t1和t2保留为左值,这将如何影响调用函数的内部?


5
投票

尚未明确的一点是static_cast<T&&>也正确处理了const T&。程序:


3
投票

可能值得强调的是,转发必须与带有转发/通用引用的外部方法结合使用。允许单独使用forward作为以下语句,但是除了引起混乱之外,它没有其他好处。标准委员会可能希望禁用这种灵活性,否则我们为什么不只使用static_cast呢?

© www.soinside.com 2019 - 2024. All rights reserved.