条款3:理解decltype

  • decltype 会推断出直觉预期的类型
const int i = 0;  // decltype(i) 为 const int

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

A a;                 // decltype(a) 为 A
bool f(const A& x);  // decltype(x) 为 const A&,decltype(f) 为 bool(const A&)
if (f(a)) {          // decltype(f(a)) 为 bool
}

int a[]{1, 2, 3};  // decltype(a) 为 int[3]
  • decltype 一般用来声明与参数类型相关的返回类型。比如下面模板的参数是容器和索引,而返回类型取决于元素类型
template <typename Container, typename Index>
auto f(Container& c, Index i) -> decltype(c[i]) {
  return c[i];  // auto 只表示使用类型推断,推断的是 decltype
}
  • C++14 允许省略尾置返回类型,只留下 auto
template <typename Container, typename Index>
auto f(Container& c, Index i) {
  return c[i];
}
  • 但直接使用会发现问题
std::vector<int> v{1, 2, 3};
f(v, 1) = 42;  // 返回 v[1] 然后赋值为 42,但不能通过编译
  • operator[] 返回元素引用,类型为 int&,但 auto 推断为 int,因此上面的操作相当于给一个整型值赋值,显然是错误的
  • 为了得到期望的返回类型,需要对返回类型使用 decltype 的推断机制,C++14 允许将返回类型声明为 decltype(auto) 来实现这点
template <typename Container, typename Index>
decltype(auto) f(Container& c, Index i) {
  return c[i];
}
  • decltype(auto) 也可以作为变量声明类型
int i = 1;
const int& j = i;
decltype(auto) x = j;  // const int& x = j;
  • 但还有一些问题,容器传的是 non-const 左值引用,这就无法接受右值
std::vector<int> makeV();  // 工厂函数
auto i = f(makeV(), 5);
  • 为了同时匹配左值和右值而又不想重载,只需要模板参数写为转发引用
template <typename Container, typename Index>
decltype(auto) f(Container&& c, Index i) {
  return std::forward<Container>(c)[i];  // 传入的实参是右值时,将 c 转为右值
}

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

decltype的特殊情况

  • 如果表达式是解引用,decltype 会推断为引用类型
int* p;  // decltype(*p) 是 int&
  • 赋值表达式会产生引用,类型为赋值表达式中左值的引用类型
int a = 0;
int b = 1;
decltype(a = 1) c = b;  // int&
c = 3;
std::cout << a << b << c;  // 033
  • 如果表达式加上一层或多层括号,编译器会将其看作表达式,变量是一种可以作为赋值语句左值的特殊表达式,因此也得到引用类型。decltype((variable)) 结果永远是引用,declytpe(variable) 只有当变量本身是引用时才是引用
int i;  // decltype((i)) 是 int&
  • 在返回类型为 decltype(auto) 时,这可能导致返回局部变量的引用
decltype(auto) f1() {
  int x = 0;
  return x;  // decltype(x) 是 int,因此返回 int
}

decltype(auto) f2() {
  int x = 0;
  return (x);  // decltype((x)) 是 int&,因此返回了局部变量的引用
}
总结:
decltype几乎总是得到一个变量或表达式的类型而不需要任何修改
对于非变量名的类型为T的左值表达式,decltype总是返回T&
C++14支持decltype(auto),它的行为就像auto,从初始化操作来推导类型,但是它推导类型时使用decltype的规则