EMCPP条款3:理解decltype

YiQi 管理员

一般用法

最基础的用法不赘述。

返回值型别尾序语法(trailing return type syntax)

1
2
3
4
5
template<typename Container, type Index>
auto authAndAccess(Container& c, Index i) -> decltype(c[i]) {
authenticateUser();
return c[i];
}

在C++14中,上面代码中的 -> decltype(c[i])其实可以省略,编译器会自动推导 c[i]作为函数返回值类型。但是,大多数含有型别 T 的对象的容器的 operator[] 会返回 T&。条款1解释说,模板型别推导过程中,初始化表达的引用性会被忽略,所以下面代码就不会通过编译:

1
2
3
std::deque<int> d;
...
authAndAccess(d, 5) = 10;

返回类型会被推导为 int,而且是个右值。

可以使用 decltype(auto) 克服上述问题:

1
2
3
4
5
template<typename Container, type Index>
decltype(auto) authAndAccess(Container& c, Index i) {
authenticateUser();
return c[i];
}

这里函数返回值为 T&,推导过程采用了 decltype 的规则。

在变量声明上,也可以使用这种形式:

1
2
3
4
Widget w;
const Widget& cw = w;
auto myWidget1 = cw; // myWidget1推导为Widget
decltype(auto) myWidget2 = cw; // myWidget2推导为const Widget&

authAndAccess 函数只能接收左值,但有时候可能需要传入一个右值,这时候可以使用万能引用:

1
2
template<typename Container, type Index>
decltype(auto) authAndAccess(Container&& c, Index i)

对未知型别的对象采用按值传递有着诸多风险:非必要的复制操作带来的性能隐患、对象截切 (slicing) 问题带来的行为异常(参见条款41),还有同行的嘲笑等。但是在容器下标这个特定问题上,遵循标准库中给出的下标值示例(例如 std::stringstd::vectorstd::dequeoperator[])应该是合理的,所以这里坚持使用了按值传递。不过,我们需要更新该模板的实现,以使它与条款25所教导我们的内容相符:对万能引用要应用 std::forward:

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

猎奇一下,如果 decltype 接收的仅有一个名字,那么行为保持不变,如果接收的是一个更复杂的表达式,它会得出左值引用。对于 int x = 0;decltype(x) 的结果是 int,但 decltype((x)) 的结果是 int&

要点速记:

  • 绝大多数情况下,decltype 会得出变量或表达式的型别而不作任何修改。
  • 对于型别为 T 的左值表达式,除非该表达式仅有一个名字,decltype 总是得出型别 T&
  • C++14支持 decltype(auto),和 auto 一样,它会从其初始化表达式出发来推导型别,但是它的型别推导使用的是 decltype 的规则 。
此页目录
EMCPP条款3:理解decltype