在 C语言里,有一个数组作为参数传给函数时,数组退化(decay)成指针的规则。
C++ 中的 auto 类型推导,也遵循着这样的规则。
int a[10]; // a is an array of 10 integers int (&ra)[10] = a; // ra1 is a reference to a auto pa = a; // pa is an pointer to a int const &rci = 1; // rci is a const reference to integer 1 auto rrci = rci; // rci is a plain integer
auto 在推导的过程中,数组退化成了指针,引用退化成了原类型,同时 cv-qualifier 被消去了。
假如我们需要其保持原类型,则需要手动加上 &, *, const, volatile 等。
类似地,函数模板参数推导的时候也有着退化的规则。
然而,decltype 却不是这样。decltype 会完全保留类型的信息。
这样的情况下,问题就产生了。我们知道 C++11 有一个特性,尾置类型推断(trailing return type)。
auto foo() -> decltype([expression])
我们的 auto,这个时候严格来说得到的是 decltype 推断出来的类型,也就是不 decay 的。
说到这里,敏锐的人或许已经能够洞悉一二 —— 我们的函数,可能会返回一个引用。
这意味着什么?假如我们的返回值是一个函数内构造的临时对象,那么我们就会访问一个被销毁的对象。
请看下面的代码:
class Test { public: Test(int _data): data(_data) { } bool operator>(Test const &_rhs) { return data > _rhs.data; } friend ostream &operator<<(ostream &os, Test const &_) { return os << _.data; } private: int data; }; template <typename _T1, typename _T2> auto _max(_T1 lhs, _T2 rhs) -> decltype(true ? lhs : rhs) { return rhs > lhs ? rhs : lhs; }
在 main 函数中,我们这么写:
Test t1(5), t2(7); Test &rt1 = t1, &rt2 = t2; cout << _max(t2, t1) << endl;
我们可以看到 _max 函数返回值的类型:Test &。事情糟起来了。如果你运行上面的代码,也会发现确实如此。
那咱们可以怎么办呢?
lhs 和 rhs 比较的结果类型是难以确定的,所以我们似乎必须依赖尾置类型推断,可我们又惧怕它返回一个引用。
解决方案
- std::decay_t
template <typename _T1, typename _T2> auto _max(_T1 lhs, _T2 rhs) -> decay_t<decltype(true ? lhs : rhs)> { return rhs > lhs ? rhs : lhs; }
2. std::common_type_t
common_type_t 会获取两个类型中范围更大的那个,我们把它作为模板的默认参数。
template <typename _T1, typename _T2, typename _RT = common_type_t<_T1, _T2>> _RT _max(_T1 lhs, _T2 rhs) { return rhs > lhs ? rhs : lhs; }
3. C++14 auto
在 C++14 中,auto 可以直接作为返回值类型用了,而不必依赖于尾置类型推断,这种情况下 auto 也会 decay:
template <typename _T1, typename _T2> auto _max(_T1 lhs, _T2 rhs) { return rhs > lhs ? rhs : lhs; }
总结一下:
类型 | 行为 |
---|---|
auto | decay |
模板函数的类型参数 T | decay |
auto -> decltype | not decay |
decltype(auto) | not decay |
auto &, T & | not decay |
auto &&, T && | reference collapsing |
reference collaping rules:
T& & == T& T& && == T& T&& & == T& T&& && == T&&