为什么这些断言适用于下面的代码?通用引用应绑定到 l 值引用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
}
但是,两个对象地址"a"和"b"在
run()
函数中是相同的。
当传递左值时,T
被推导为左值引用,即int&
.(int& &&
折叠为int&
,因此函数参数a
的类型为int&
。然后将b
声明为与a
绑定的引用。
当传递右值时,T
被推导为int
。(因此,函数参数a
的类型为int&&
。然后将b
声明为从a
复制的自变量。
在run(value)
中,value
是一个左值,它需要与T&&
匹配。左值不能绑定到右值引用,所以T = int
和T = 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
是一个新的(复制/移动的(对象(,并处理数组和函数(即使a
不是,也要b
成为指针(。
怀疑你需要它们,但[temp.deduct.call]/3
谈到了转发引用的模板推导,[dcl.init.list]/3.9
说列表初始化引用只是将其绑定到初始值设定项列表的元素。也[forward]
,好吧,解释std::forward<T>
.基本上,如果T
是左值引用,则std::forward<T>(x)
是左值,否则是x值(一种右值(。(基本上这是一个有条件的std::move
。