玩命加载中 . . .

03-理解decltype


相比模板类型推导和auto类型推导,decltype只是简单的返回名字或者表达式的类型

const int i = 0;                //decltype(i)是const int

bool f(const Widget& w);        //decltype(w)是const Widget&
                                //decltype(f)是bool(const Widget&)

struct Point{
    int x,y;                    //decltype(Point::x)是int
};                              //decltype(Point::y)是int

Widget w;                       //decltype(w)是Widget

if (f(w))//decltype(f(w))是bool

template<typename T>            //std::vector的简化版本
class vector{
public:
    T& operator[](std::size_t index);
};

vector<int> v;                  //decltype(v)是vector<int>if (v[0] == 0)//decltype(v[0])是int&

在C++11中,decltype最主要的用途就是用于声明函数模板,而这个函数返回类型依赖于形参类型

例如要根据容器和下标返回指定下标的元素

template<typename Container, typename Index>    //可以工作,但是需要改良
auto authAndAccess(Container& c, Index i) ->decltype(c[i])
{
    authenticateUser();
    return c[i];
}

函数名称前面的auto不会做任何的类型推导工作。相反的,他只是暗示使用了C++11的尾置返回类型语法,尾置返回类型的好处是我们可以在函数返回类型中使用函数形参相关的信息。在authAndAccess函数中,我们使用ci指定返回类型。如果我们按照传统语法把函数返回类型放在函数名称之前,ci就未被声明所以不能使用

C++14标准下我们可以忽略尾置返回类型,只留下一个auto

template<typename Container, typename Index>    //C++14版本,
auto authAndAccess(Container& c, Index i)       //不那么正确
{
    authenticateUser();
    return c[i];                                //从c[i]中推导返回类型
}

函数返回类型中使用auto,编译器实际上是使用的模板类型推导的那套规则。operator[]对于大多数T类型的容器会返回一个T&,但是在模板类型推导期间,表达式的引用性会被忽略

std::deque<int> d;
authAndAccess(d, 5) = 10;               //返回d[5],然后把10赋值给它
                                        //无法通过编译器

在这里d[5]本该返回一个int&,但是模板类型推导会剥去引用的部分,因此产生了int返回类型。函数返回的那个int是一个右值,上面的代码尝试把10赋值给右值int,C++11禁止这样做,所以代码无法编译

要想让authAndAccess像我们期待的那样工作,我们需要使用decltype类型推导来推导它的返回值,即指定authAndAccess应该返回一个和c[i]表达式类型一样的类型。C++期望在某些情况下当类型被暗示时需要使用decltype类型推导的规则,C++14通过使用decltype(auto)说明符使得这成为可能:auto说明符表示这个类型将会被推导,decltype说明decltype的规则将会被用到这个推导过程中

template<typename Container, typename Index>    //C++14版本,
decltype(auto)                                  //可以工作,
authAndAccess(Container& c, Index i)            //但是还需要
{                                               //改良
    authenticateUser();
    return c[i];
}

现在authAndAccess将会真正的返回c[i]的类型。一般情况下c[i]返回T&authAndAccess也会返回T&,特殊情况下c[i]返回一个对象,authAndAccess也会返回一个对象

decltype(auto)的使用不仅仅局限于函数返回类型,当你想对初始化表达式使用decltype推导的规则,也可以使用

Widget w;

const Widget& cw = w;

auto myWidget1 = cw;                    //auto类型推导
                                        //myWidget1的类型为Widget
decltype(auto) myWidget2 = cw;          //decltype类型推导
                                        //myWidget2的类型是const Widget&

再看看C++14版本的authAndAccess声明:

template<typename Container, typename Index>
decltype(auto) authAndAccess(Container& c, Index i);

如果想支持右值,可以用通用引用和完美转发

template<typename Containter, typename Index>   //最终的C++14版本
decltype(auto)
authAndAccess(Container&& c, Index i)
{
    authenticateUser();
    return std::forward<Container>(c)[i];
}

C++11中要使用尾置返回类型

template<typename Container, typename Index>    //最终的C++11版本
auto
authAndAccess(Container&& c, Index i)
->decltype(std::forward<Container>(c)[i])
{
    authenticateUser();
    return std::forward<Container>(c)[i];
}

一些特殊情况,例如

int x = 0;

其中,x是一个变量的名字,所以decltype(x)int。但是如果用一个小括号包覆这个名字,比如这样(x) ,就会产生一个比名字更复杂的表达式。对于名字来说,x是一个左值,C++11定义了表达式(x)也是一个左值。因此decltype((x))int&。用小括号覆盖一个名字可以改变decltype对于名字产生的结果

所以

decltype(auto) f1()
{
    int x = 0;
    return x;                            //decltype(x)是int,所以f1返回int
}

decltype(auto) f2()
{
    int x = 0;
    return (x);                          //decltype((x))是int&,所以f2返回int&
}

注意不仅f2的返回类型不同于f1,而且它还引用了一个局部变量

请记住:

  • decltype总是不加修改的产生变量或者表达式的类型
  • 对于T类型的不是单纯的变量名的左值表达式,decltype总是产出T的引用即T&
  • C++14支持decltype(auto),就像auto一样,推导出类型,但是它使用decltype的规则进行推导

文章作者: kunpeng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kunpeng !
  目录