如果可能,C++ 通过右值引用传递参数,否则复制左值引用

C++ pass parameter by rvalue reference if possible, otherwise copy the lvalue reference

使用右值引用,许多冗余副本可能会被省略,但这似乎需要我多次编写相同的函数(一次用于右值引用,一次用于 const 左值引用)。但是标准库好像有些函数只需要声明一次。

例如:

#include <iostream>
#include <tuple>

void foo(int&& x){
    x = 2;
}

int main()
{
    int x = 1;
    foo(x); // compile error
    std::make_tuple(x); // ok
    std::cout << x << std::endl;
}

调用foo(x)是编译错误,因为我无法从int隐式转换为int&&。但是我很困惑为什么 std::make_tuple 会起作用。 reference 表示它只接受右值引用参数。当传递给它的值是一个 ravlue 引用时,它似乎也不会制作副本,但是当在我上面的示例中使用时它会制作副本(正如大多数人所期望的那样)。

如何让 foo 像这样工作?

这对你有用吗?

#include <iostream>
#include <tuple>

void foo(int&& x){
    std::cout<<"rvalue"<<std::endl;
    x = 2;
}

void foo(int& x){
    std::cout<<"lvalue"<<std::endl;
    x = 2;
}

int main()
{
    int x = 1;
    foo(x); // no compile error anymore
    foo(std::move(x)); // now r-value is being used        
    std::make_tuple(x); // ok
    std::cout << x << std::endl;
}

The reference says that it only accepts rvalue reference parameters.

不是,这是forwarding reference,根据传入参数的值类别,它既可以作为左值引用也可以作为右值引用

How can I make foo work like this?

声明转发引用的要点是(1)类型推导是必要的,这意味着你需要在这里制作foo一个函数模板; (2) 参数 x 对于模板参数 T 具有 T&& 的确切形式。例如

template <typename T>
void foo(T&& x){
    x = 2;
}

然后

int x = 1;
foo(x);   // lvalue passed, T is deduced as int&, parameter's type is int&
foo(1);   // rvalue passed, T is deduced as int, parameter's type is int&&

请注意,std::make_tuple 也是如此,即使它使用的是模板参数包。最好记住,即使转发引用看起来像右值引用,但它们是不同的东西。

顺便说一句:std::forward 通常与转发引用一起使用以保留参数的值类别,例如当转发到其他功能时。

std::make_tuple 之所以有效,是因为该函数实际上并不像 foo 那样采用右值引用。 std::make_tuple 采用 T&& 形式的对象,并且由于 T 是模板类型,它使其成为转发引用,而不是右值引用。转发引用可以绑定到左值和右值,而右值引用只能采用右值。要使 foo 相同,您需要

template<typename T>
void foo(T&& bar)
{
    bar = 2;
}

差异是 && 运算符与模板参数一起使用时相当微妙的重载的结果:

template<typename foo>
void bar(foo &&baz)

这不是右值引用。它是转发参考。解析模板后,baz 将成为左值或右值引用,具体取决于调用情况。

这就是为什么您会看到 C++ 库提供看似单一的模板,它在左值和右值上下文中都有效。

但转发引用仅出现在模板中。在非模板声明中:

void bar(int &&baz)

这始终是右值引用,并且只能在右值上下文中使用。