我正在使用 gcc12.2 并发现以下代码编译并产生奇怪的结果在 godbolt 中尝试。 (PS:切换到clang也是一样的结果)
#include <iostream>
void global() { /*std::cout << "global()" << std::endl;*/ }
template <typename T> // universal reference
void invoke(T&& func, const std::string& tag) {
if constexpr (std::is_rvalue_reference_v<T>) {
std::cout << "rvalue reference " << tag << std::endl;
}
else if constexpr (std::is_lvalue_reference_v<T>) {
std::cout << "lvalue reference " << tag << std::endl;
}
else {
std::cout << "non-reference " << tag << std::endl;
}
func();
}
template <typename T>
struct Test {
Test(T&& x, const std::string& tag) // rvalue reference, not universal reference
{
if constexpr (std::is_rvalue_reference_v<T>) {
std::cout << "rvalue reference " << tag << std::endl;
}
else if constexpr (std::is_lvalue_reference_v<T>) {
std::cout << "lvalue reference " << tag << std::endl;
}
else {
std::cout << "non-reference " << tag << std::endl;
}
}
};
int main() {
int && x = 3;
using RRef = void (&&)();
RRef rref = global;
using LRef = void (&)();
LRef lref = global;
std::cout << "RRef is rvalue reference " << std::is_rvalue_reference_v<RRef> << std::endl;
std::cout << "rref is rvalue reference " << std::is_rvalue_reference_v<decltype(rref)> << std::endl;
std::cout << "move(rref) is rvalue reference " << std::is_rvalue_reference_v<decltype(std::move(rref))> << std::endl;
std::cout << "x is rvalue reference " << std::is_rvalue_reference_v<decltype(x)> << std::endl;
std::cout << "move(x) is rvalue reference " << std::is_rvalue_reference_v<decltype(std::move(x))> << std::endl;
std::cout << "==== invoke ==== " << std::endl;
invoke(global, "global");
invoke(rref, "rref");
invoke(std::move(rref), "rref2");
invoke(lref, "lref");
invoke(std::move(lref), "lref2");
std::cout << "==== Test ==== " << std::endl;
Test(global, "global");
Test(rref, "rref");
Test(std::move(rref), "rref2");
Test(lref, "lref");
Test(std::move(lref), "lref2");
std::cout << "==== Test int ==== " << std::endl;
// Test(x, "x"); // cannot bind lvalue to rvalue-reference
Test(std::move(x), "move(x)");
}
gcc 12.2 的输出如下:
RRef is rvalue reference 1
rref is rvalue reference 1
move(rref) is rvalue reference 0 // why is this no longer rvalue reference
x is rvalue reference 1
move(x) is rvalue reference 1
==== invoke ====
lvalue reference global // why are they all lvalue reference
lvalue reference rref
lvalue reference rref2
lvalue reference lref
lvalue reference lref2
==== Test ====
non-reference global // why they are non-reference
non-reference rref
non-reference rref2
non-reference lref
non-reference lref2
==== Test int ====
non-reference move(x)
能否请您解释一下为什么我们得到以下输出:
std::move(rref)
是左值引用而 std::move(x)
是右值引用invoke
时,推导的类型都是左值引用,说明传递给invoke
Test
时,各种输入都被接受,说明它们都是右值引用。传递
int
的引用表现正常,而传递函数的引用表现得非常奇怪。
尽管
std::move(x)
是右值引用,但 std::move(rref)
是左值,因为它是标准中的特例。来自 C++14 标准草案 [expr.call/p10]:
如果结果类型是 左值引用类型或对函数类型的右值引用,则函数调用是左值,如果结果类型是对对象类型的右值引用,则为xvalue,否则为纯右值。
这在这个答案中有详细解释。
将值传递给
invoke()
时,您可以传递它们
invoke(global, "global");
invoke(rref, "rref");
std::move(ref-to-function)
:
invoke(std::move(rref), "rref2");
由于(1)中提到的特殊情况,这也是一个左值。因此,模板参数在所有情况下都绑定到左值。
请注意,您的
Test
构造函数打印有关模板参数类型 T
的信息,而不是有关参数类型 x
(即 T&&
)的信息。这就是为什么您在期望“右值引用”的地方看到“非引用”的原因。如果我们替换代码以打印有关 decltype(x)
的信息而不是 T
,我们将得到预期的“右值引用”结果。
在任何情况下,rvalue-reference-to-function 的使用都非常罕见和晦涩,通常左值引用足以满足大多数需求(尽管 here 是一个需要右值引用的例子)。