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

posted @ 2019-12-11 14:57  Irene_f  阅读(1201)  评论(0编辑  收藏  举报