在 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 比较的结果类型是难以确定的,所以我们似乎必须依赖尾置类型推断,可我们又惧怕它返回一个引用。

解决方案

  1. 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; }

总结一下:

类型行为
autodecay
模板函数的类型参数 Tdecay
auto -> decltypenot decay
decltype(auto)not decay
auto &, T &not decay
auto &&, T &&reference collapsing

reference collaping rules:

T& &   == T&
T& &&  == T&
T&& &  == T&
T&& && == T&&

Offensive77

A little learning is a dangerous thing.

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注