条款3.理解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>
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

采用了这样一个形式后,operator[]返回值是什么类型,authAndAccess的返回值就是什么类型。

c++11允许对单表达式的lambda式的返回值实施推导,c++14则将这个允许范围扩张到了一切lambda式和一切函数。对于authAndAccess这种情况来说,这就意味着在c++14中可以去掉尾置返回类型语法,只保留auto

template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i)
{
    authenticateUser();
    return c[i];	//返回值类型是根据c[i]推导出来的
}

条款2解释说,编译器会对auto指定为返回类型的函数实现模板类型推导。这样会留下隐患,大多是含有类型T的对象的容器的operator[]会返回T&。条款1解释说,模板类型推导过程中,初始化表达的引用性会被忽略。

std::deque<int> d;
authAndAccess(d, 5) = 10;	//验证用户,并返回d[5]
							//然后将其赋值为10
							//这段代码无法通过编译

d[5]返回的是int&,但是对authAndAccess的返回值实施auto类型推导将剥去引用饰词,这样一来返回值就变成了int。作为函数的返回值,该int是个右值,所以上述代码其实尝试将10赋给一个右值int。这在c++中属于被禁止的行为,所以代码无法通过编译。

想要让authAndAccess如我们期望般运行,就要对其返回值实施decltype类型推导,即指定authAndAccess的返回值类型与表达式c[i]返回的类型完全一致。

在c++14中通过decltype(auto)饰词解决了这个问题

template<typename Container, typename Index>
decltype(auto) authAndIndex(Container& c, Index i)
{
    authenticateUser();
    return c[i];
}

现在,authAndAccess的返回值类型真的与c[i]返回的类型一致了。

decltype(auto)并不限于在函数返回值类型处使用。在变量声明的场合上,也可以使用

Widget w;
const Widget& cw = w;

auto myWidget = 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);

容器的传递方式是对非常量的左值引用,因为返回该容器的某个元素的引用,就意味着允许客户对容器进行修改。不过这也意味着无法向该函数传递右值容器。右值是不能绑定到左值引用的(除非是对常量的左值引用)。

必须承认,向authAndAccess传递右值容器属于罕见情况。一个右值容器,作为一个临时对象,一般而言会在包含了调用authAndAccess的语句结束处被析构。而这就是说,该容器中某个元素的引用会在创建它的那个语句结束时被置于空悬状态。但即使如此,向authAndAccess传递一个临时对象仍然可能是合理行为。客户可能就是想要制作该临时容器的某元素的一个副本。

std::deque<std::string> makeStringDeque();	//工厂函数

auto s = authAndAccess(makeStringDeque(), 5);

容器的传递方式是对非常量的左值引用,因为返回该容器的某个元素的引用,因为返回该容器的某个元素的引用,就意味着允许客户对容器进行修改。不过这也意味着无法向该函数传递右值容器。右值不能绑定到左值引用

如果要支持这种用法,就得修改authAndAccess的声明,同时接受左值和右值。可以声明成万能引用。对万能引用要应用std::forward

template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c,Index i)
{
    authenticateUser();
    return std::forward<Container>(c)[i];
}

以下是C++11的版本。它和C++14版本几乎一样,只是你需要自行指定返回值类型。

template<typename Container, typename Index>
auto authAndAccess(Container&& c, Index i)->decltype(std::forward<Container>(c)[i])
{
    authenticateUser();
    return std::forward<Container>(c)[i];
}

decltype应用于一个名字之上,就会得出该名字的声明类型。名字其实是左值表达式,但如果仅有一个名字,decltype的行为保持不变

如果是更复杂的左值表达式,decltype就保证得出的类型总是左值引用。换言之,只要一个左值表达式不仅是一个类型为T的名字,它就得出一个T&类型

int x = 0;

其中x是一个变量名字,所以decltype(x)的结果是int。但是如果把x放入一对小括号中,就得到了一个比仅有名字更复杂的表达式(x)。作为一个名字,x是一个左值,在c++定义中,表达式(x)也是一个左值,所以decltype((x))的结果就是int&

在c++14中,上述的性质如果和decltype(auto)联合一下,就会影响到函数的类型推导结果。

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返回了一个局部变量的引用。这是错误的。

要点速记

  • 绝大多数情况下,decltype会得出变量或表达式的类型而不做任何修改
  • 对于类型为T的左值表达式,除非该表达式仅有一个名字,decltype总是得出类型T&
  • c++14支持decltype(auto),和auto一样,它会从初始化表达式出发来推导类型,但是它的类型推导使用的是decltype的规则
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值