remove_reference 原理
模板中的引用参数
在模板中,双 '&
‘ 会被解析为“引用”,这个“引用”可以是“左值”引用,也可以是“右值”引用。
例如:
template <typename T> void func(T &&a) { std::cout << "a = " << a << std::endl; } int a = 123; func(a); // 被解析为左值引用 func(123); // 被解析为右值引用
我们查看一下编译器是如何分析类型的:
执行:
nm -C ./a.out
可以得到如下结果:
这里说一下:func(a)
对应的是 void func<int &>(int &)
func(123)
对应的是 void func<int>(int &&)
可以发现:
左值 a
被解析为 int &
;
右值 123
被解析为 int &&
。
因此,可以发现:
在模板函数(参数为双&&)中,所有模板参数都会被转换为引用
这不禁让人产生疑惑,我的模板参数明确填写了为双 &
为什么还能被转换为 单 ‘&’ 呢?
引用折叠
在C++的模板中具有一条潜规则:引用折叠。
引用折叠的意思大概就是:奇数的 &
被看作左值引用,偶数个 &
被看作右值引用。
当你想向模板函数中传入引用时,不管是左值引用还是右值引用,你都需要去写作 &&
类型。
因为编译器是这样理解你的模板类型的:
首先,写成 &&
会让编译器认为你这里是传入一个 引用
接下来,接下来,不管是左值还是右值,编译器都会将其解析为引用的状态:
例如:
int a = 123; // 左值,被解析为 int & 123; // 右值,被解析为 int &&
被解析之后的类型会被完全替换到模板参数中,例如:
template <typename T> void func(T &&a) {...}
中的 T
会被完全替换:
void func(int & &&a) {...} // int a = 123; void func (int && &&a) {...} // 123;
因此,可以发现 &
产生了很多个,而编译器就需要根据
奇数的
&
被看作左值引用,偶数个&
被看作右值引用
这条原则来进行判断是左值引用还是右值引用。
remove_reference 的原理
remove_reference
是一个工具类,用于将引用类型提取出来(也就是去掉引用),例如:
template <typename T> void func(T &&a) { typename std::remove_reference<T>::type c; ... } int main() { int n = 123; func(n); // 这会在函数内部生成一个类型为int类型的变量c func(123); // 这也会在函数内部生产一个类型为int类型的变量c }
这东西的原理是什么呢?实际上利用了模板的特化:
template <typename T> // 非特化 struct remove_reference { using type = T; }; template <typename T> // 特化一 struct remove_reference<T &> { using type = T; }; template <typename T> // 特化二 struct remove_reference<T &&> { using type = T; };
是不是很简单?
通过第一个特化版本就可以将左值引用的类型提取出来,通过第二个特化版本就可以将右值引用的类型提取出来。