C++型别推导的“坑”——忽略引用

无论是模板还是auto的型别推导,绝大部分情况下都会忽略引用。举个栗子:

template<typename T>
void f(T& param) {
    T temp = param;
    temp = 5;
}

//main函数中:
int x = 2;
int& rx = x;
f(rx);
cout << x;

若T为引用类型,则对temp的赋值应该改变了x的值,因此输出x应该得到5;若为2则说明T只是int类型,temp是一个拷贝而非引用。最终输出结果为2。auto的型别推导同样。只有一个情况例外:当形参是万能指针而实参是左值时,T为左值引用。将上面代码中f的形参类型改为T&&,此时再运行代码发现输出为5,说明T为int&。

那么问题来了。
我们有时会忘记型别推导忽略引用这个特点,从而写出类似于下面这样的代码:

/*向该函数传入一个容器和一个下标;函数返回位于这个容器该下标位置的元素的引用*/
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)并非左值,不能被赋值。大多数容器的operator []会返回T&,那么auto推导出来find_ele的返回值类型和c[i]一样,不也应该是引用吗?对哒,型别推导会忽略引用哒。因此在本例中,推导得到的find_ele返回值的类型是int而非int&。
如果希望将引用作为返回,可以像下面这样写函数头部(仅考虑实参为左值的情况):

//(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在一般情况下都会忠实地返回参数自身的类型而不做更改。

猜你喜欢

转载自www.cnblogs.com/saltedreed/p/12022576.html