Type Deduction - Decltype in C++

Declaration Type

decltype 也是在 C++11 引入的关键字,跟模板推导和 auto 类型推断的不同是:decltype 会准确地告诉你表达式所声明类型的实际类型。一般而言,decltype 推导出的类型和你预料中的所一致,它会保留表达式中的 CV qualifier、还可以帮你判断表达式到底是左值还是右值,它一律会帮你分析出来。

我们先用一些简单的例子来看看 decltype 是如何推导变量类型的:

int i = 10; // i is declared int type
decltype(i) j = 10; // j has the same type as i

const int ci = 10; // ci is declared const int 
decltype(ci) cj = 10; // cj's type is const int

const int& cri = 10; // cri is declared const int&
decltype(cri) = 10; // thus the cri's type is const int&

std::vector<int> v(10) = {}; // v is declared std::vector<int>
v[0] = 0; // v[0] is int& (T& operatorp[](){})

是不是感觉简单明了,甚至比 auto 还简单?在实际应用上,decltype 用途最多的还是函数返回值推导。

Return Type Deduction

auto 小节中,我们介绍了它的行为和函数模板很像,这就导致有时候它会说 auto 在推断函数返回值时忽略本应出现的引用,比方说:

template<typename Container, typename Index>
auto accessAndModify(Container& c, Index i) {
	return c[i]; // should return a T&, right?
}

std::vector<int> v(10);
accessAndModify(v, 0) = 10; // error

一般而言,operator[] 都会返回 T& 类型,而在上面的情况中,auto 会将返回值推断为 T,如果你对 auto 的推断规则不是很熟悉或者单纯忘记了,那这类错误就很容易出现。为了解决这个问题,要么在 auto 后面加上 &,要么使用 decltype 进行更精细的类型推断,如:

// decltype in C++11 
template<typename Container, typename Index>
auto accessAndModify(Container& c, Index i) -> decltype(c[i]) {
	return c[i];
}

std::vector<int> v(10);
accessAndModify(v, 0) = 10; 

这里,你会发现返回类型被放在了参数列表的后面,这种语法被称为返回类型后置 (trailing return type),这种语法尤其适合泛型编程:

template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
    return a + b;
}

在 C++14,你还可以怎么写:

// decltype after C++14
template<typename Container, typename Index>
decltype(auto) accessAndModify(Container& c, Index i) {
	return c[i];
}

std::vector<int> v(10);
accessAndModify(v, 0) = 10; // ok

decltype(auto) 是 C++14 引入的,它是 decltypeauto 的结合,它用于简化原先的表达(C++11 是需要在 decltype 中填入表达式,而 C++14 只需要填 auto)。这里, auto 表示要推导的类型,而 decltype 表示在推导的过程中使用 decltype 的推导规则,即获取更精确的类型。

decltype(())

decltype 的类型推导已经能让我感觉到心满意足了,在最开始,我们说它“还可以帮你判断表达式到底是左值还是右值”。这就需要你对表达式做出一些修改:

int i = 0;  // decltype(i) is int
			// decltype((i)) is int&
decltype(auto) func() {
	int i = 0;
	return (i);
}

decltype((auto)) 是非法的。