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

template <typename T1, typename T2>
void outer(T1&& t1, T2&& t2) 
{
    inner(std::forward<T1>(t1), std::forward<T2>(t2));
}

当前回答

从另一个角度来看,在通用引用赋值中处理右值时,最好保持变量的类型不变。例如

auto&& x = 2; // x is int&&
    
auto&& y = x; // But y is int&    
    
auto&& z = std::forward<decltype(x)>(x); // z is int&&

使用std::forward,我们确保z与x具有完全相同的类型。

此外,std::forward不会影响左值引用:

int i;

auto&& x = i; // x is int&

auto&& y = x; // y is int&

auto&& z = std::forward<decltype(x)>(x); // z is int&

z仍然和x有相同的类型。

回到你的例子,如果内部函数对int&和int&&有两个重载,你想传递的变量是z赋值,而不是y赋值。

示例中的类型可以通过以下方式进行评估:

std::cout<<is_same_v<int&,decltype(z)>;
std::cout<<is_same_v<int&&,decltype(z)>;

其他回答

还有一点没有说清楚,static_cast<T&&>也正确地处理了const T&。 计划:

#include <iostream>

using namespace std;

void g(const int&)
{
    cout << "const int&\n";
}

void g(int&)
{
    cout << "int&\n";
}

void g(int&&)
{
    cout << "int&&\n";
}

template <typename T>
void f(T&& a)
{
    g(static_cast<T&&>(a));
}

int main()
{
    cout << "f(1)\n";
    f(1);
    int a = 2;
    cout << "f(a)\n";
    f(a);
    const int b = 3;
    cout << "f(const b)\n";
    f(b);
    cout << "f(a * b)\n";
    f(a * b);
}

生产:

f(1)
int&&
f(a)
int&
f(const b)
const int&
f(a * b)
int&&

注意,f必须是一个模板函数。如果它被定义为void f(int&& a)这是行不通的。

从另一个角度来看,在通用引用赋值中处理右值时,最好保持变量的类型不变。例如

auto&& x = 2; // x is int&&
    
auto&& y = x; // But y is int&    
    
auto&& z = std::forward<decltype(x)>(x); // z is int&&

使用std::forward,我们确保z与x具有完全相同的类型。

此外,std::forward不会影响左值引用:

int i;

auto&& x = i; // x is int&

auto&& y = x; // y is int&

auto&& z = std::forward<decltype(x)>(x); // z is int&

z仍然和x有相同的类型。

回到你的例子,如果内部函数对int&和int&&有两个重载,你想传递的变量是z赋值,而不是y赋值。

示例中的类型可以通过以下方式进行评估:

std::cout<<is_same_v<int&,decltype(z)>;
std::cout<<is_same_v<int&&,decltype(z)>;

我认为有一个概念性代码实现std::forward可以帮助理解。这张幻灯片来自Scott Meyers的演讲An Effective c++ 11/14 Sampler

代码中的move函数是std::move。在前面的讨论中有一个(正在工作的)实现。我在libstdc++中找到了std::forward的实际实现,在文件move.h中,但它根本没有指导意义。

从用户的角度来看,它的含义是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

代码基于前面提到的演讲中的一个示例。第10张,从开始的15点开始。

值得强调的是,forward必须与带有forward /universal引用的外部方法一起使用。使用forward本身作为下面的语句是允许的,但是除了引起混乱之外没有任何好处。标准委员会可能希望禁用这种灵活性,否则我们为什么不直接使用static_cast呢?

     std::forward<int>(1);
     std::forward<std::string>("Hello");

在我看来,move和forward是设计模式,是r值参考类型引入后的自然结果。我们不应该假定一个方法被正确地使用,除非禁止不正确的使用。

在完全转发中,std::forward用于将命名右值引用t1和t2转换为未命名右值引用。这样做的目的是什么?如果我们将t1和t2保留为左值,会对被调用函数内部产生什么影响? template <typename T1, typename T2> void outer(T1&& T1, T2&& T2) { 内部(std:: < T1 > (T1)、std::转发< T2 > (T2)); }

如果在表达式中使用了命名的右值引用,它实际上是一个左值(因为您通过名称引用对象)。考虑下面的例子:

void inner(int &,  int &);  // #1
void inner(int &&, int &&); // #2

现在,如果我们像这样调用外层

outer(17,29);

we would like 17 and 29 to be forwarded to #2 because 17 and 29 are integer literals and as such rvalues. But since t1 and t2 in the expression inner(t1,t2); are lvalues, you'd be invoking #1 instead of #2. That's why we need to turn the references back into unnamed references with std::forward. So, t1 in outer is always an lvalue expression while forward<T1>(t1) may be an rvalue expression depending on T1. The latter is only an lvalue expression if T1 is an lvalue reference. And T1 is only deduced to be an lvalue reference in case the first argument to outer was an lvalue expression.