GCC/Clang中全局函数引用推导异常问题咨询
C++函数引用与通用/右值引用的行为解析
测试代码
#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)"); }
编译输出
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)
疑问与解析
1. std::move(rref)被判定为非右值引用,而std::move(x)为右值引用
核心原因是函数右值引用的特殊性:
- 对普通对象右值引用
x调用std::move,返回的是int&&,符合常规右值引用判定,所以std::is_rvalue_reference_v返回1。 - 对函数右值引用变量
rref调用std::move时,std::move返回的T&&(T是void (&&)())会触发引用折叠,最终得到void() &&。但C++标准规定,函数类型的右值引用会被调整为函数类型本身(因为函数无法被移动,只能被引用),所以decltype(std::move(rref))的实际类型是void(),自然std::is_rvalue_reference_v返回0。
2. 向通用引用的invoke传递函数相关参数时,推导类型均为左值引用
通用引用的推导规则是:传入左值则T推导为左值引用,传入右值则T推导为非引用类型。但所有传入invoke的函数相关参数本质都是左值:
- 函数名
global本身是左值表达式; - 不管是左值引用还是右值引用变量(
rref、lref),它们作为变量都是左值表达式; std::move(rref)返回的函数类型void(),会被隐式转换为函数左值引用(函数类型无法直接传递,会退化为左值引用)。
因此所有参数都被识别为左值,T推导为左值引用类型,输出都是lvalue reference。
3. 向仅接受右值引用的Test构造函数传递函数相关参数时,全部被接受且推导为非引用类型
Test的构造函数不是通用引用(T是类模板参数,而非函数模板参数),推导逻辑如下:
- 函数名、函数引用变量、
std::move(rref)传递时,都会被推导为非引用的函数类型void(),因为C++会自动剥离引用属性来匹配T&&; - 函数类型的
T&&(即void() &&)可以接受函数类型、函数左/右值引用参数(函数引用会隐式转换为函数类型); - 最终
T是无引用的函数类型,所以std::is_rvalue_reference_v<T>和std::is_lvalue_reference_v<T>都返回false,输出non-reference。
而int类型的Test(std::move(x))中,T被推导为int(非引用),T&&匹配传入的int&&,输出non-reference,符合预期。
内容的提问来源于stack exchange,提问作者doraemon
相关产品推荐
相关产品推荐

