C++型别推导的“坑”——忽略引用
无论是模板还是auto的型别推导,绝大部分情况下都会忽略引用。举个栗子:
template<typename T>
void f(T param) {
如果现在有int类型的变量x和它的引用rx,当rx传给函数f时,我们的直觉是T应该被推导为引用,由此直觉还认为定义一个T类型的变量temp并用param赋值后,temp和param应该都是x的引用。然而直觉是错误的,这里T会被推导为int,而非int&。可以用代码测试一下,如果temp也是引用,那么对temp另外赋值应该会更改x的值:
template<typename T>
void f(T param) {
T temp = param;
temp = 5; // 若temp是引用,那么这条语句会把x的值改为5
}
//main函数中:
int x = 2;
int& rx = x;
f(rx); //实参为int&
cout << x; // 看看x的值是否被改变
最终输出结果为2,说明T只是int类型,temp是一个拷贝而非引用。
auto的型别推导同样。
只有一个情况例外:当形参是万能指针而实参是左值时,T为左值引用。也就是说将上面代码中f的形参类型改为T&&
,此时再运行代码会发现输出为5,说明T为int&。
我们有时会忘记型别推导忽略引用这个特点。比如,大多数容器的operator []会返回T&,因此当我们希望写一个函数,该函数可以返回位于某容器特定下标位置的元素时,我们可能会写出这样的代码:
/*向该函数传入一个容器和一个下标;函数返回位于这个容器该下标位置的元素的引用*/
template<typename Container, typename Index>
auto find_ele(Container& c, Index i) {
do_sth();
return c[i];
}
然后会像下面这样来使用这个函数:
find_ele(arr, 3) = 5;
然后会发现报错了。编译器会说find_ele(arr, 3)并非左值,不能被赋值。这就是因为虽然return c[i]
返回的是引用,但auto
型别推导时却会忽略引用。
如果希望将引用作为返回,可以像下面这样写函数头部(仅考虑实参为左值的情况):
//(1)
auto& find_ele(Container& c, Index i)
//(2)
auto find_ele(Container& c, Index i) -> decltype(c[i])
//(3)
decltype(auto) find_ele(Container& c, Index i)
(1)显式规定返回值必须是引用;(2)和(3)都使用了decltype,这是因为decltype在一般情况下都会忠实地返回参数自身的类型而不做更改。