通用参考左值不复制对象

Universal reference l-value not copying object

为什么这些断言适用于以下代码?通用引用应绑定到左值引用 run(T& a),并从 a 复制一个对象 b。然而,对象地址 "a" 和 "b" 在 run() 函数中是相同的。使用 C++11/14/17/2a gcc-9.2 和 clang++-6.0 进行测试。标准的哪一部分说这是有效的?没有找到任何相关内容。

#include <cassert>
#include <utility>

template <typename T>
void run(T&& a)
{
  T b {std::forward<T>(a)};
  ++b;
  assert(b == a);
  assert(&a == &b);
}

int main()
{
  int value {10};
  run(value); // asserts work, not expected
  // run(std::move(value)); // the asserts don't work as expected
}

However both objects addresses "a" and "b" are the same in the run() function.

传递左值时,Tdeduced as lvalue-reference, i.e. int&. (int& && int&,所以函数参数a的类型是int&。)然后b 声明为绑定到 a.

的引用

当传递一个右值时,T 被推断为 int。 (所以函数参数a的类型是int&&。)然后b声明为从a复制的自变量。

run(value)中,value是一个左值,需要和T&&匹配。左值不能绑定到右值引用,所以 T = intT = int&& 不会,就像 T&& = int&& 那样。唯一有效的是 T = int&。由于引用折叠,对左值引用的右值引用是左值引用,因此 run 的实例化如下:

template<>
void run<int&>(int &a) {
    int &b{a}; // expanding std::forward
    ++b;
    assert(b == a);
    assert(&b == &a);
}

显然,断言总是通过的。现在,对于 run(std::move(value)),参数确实是一个右值,你得到 T = int。然后

template<>
void run<int>(int &&a) {
    int b{std::move(a)};
    ++b;
    assert(b == a);
    assert(&b == &a);
}

这当然失败了。也许你应该更换

T b{std::forward<T>(a)};

std::decay_t<T> b{std::forward<T>(a)};

这将从 T 中删除引用(确保 b 是一个新的 (copied/moved) 对象)并且还会处理数组和函数(通过使 b 成为指针即使 a 不是)。

怀疑您是否需要它们,但是 [temp.deduct.call]/3 talks about the template deduction of forwarding references, and [dcl.init.list]/3.9 says that list-initializing a reference just binds it to the element of initializer list. Also [forward],嗯,解释 std::forward<T>。基本上,如果 T 是一个左值引用,那么 std::forward<T>(x) 就是一个左值,否则就是一个 xvalue(一种右值)。 (基本上是有条件的std::move。)