Item 1: 理解模板类型推断
Item 2: auto语句中的类型推断
Item 3: 理解decltype
Item 4: 如何观测实际的类型推断
本文所有内容参考于 《Effective Modern C++》(Scott Meyers)一书,仅供个人学习
Deducing Types (类型推断)
Item 1: 理解模板类型推断
模板函数声明中的类型
1 | template<typename T> |
ParamType为普通引用,即 ParamType 为 T&
1 | template<typename T> |
1 | void f(T& param); |
ParamType 为 const T&
1 | void f(const T& param); |
ParamType 为右值引用 T&&,
1 | void f(T&& param); |
在《C++ Primer》16.2.5中介绍了这种情况:“当我们将一个左值传递给函数的优质引用参数,且此右值引用指向模板类型参数(如T&&)时,编译器推断模板类型参数为实参的左值引用类型。”因此f(x)中T为int&, f(cx)中T为const int&。同时,“我们不能(直接)定义一个引用的引用”,这时有另外的一个规则,叫引用折叠
1 | 引用折叠: |
ParamType 是值,T
1 | void f(T param); |
会将传入的值去引用,并去const。
当传入数组会怎么样? 是的,聪明的你会发现下面的格式是错的
1 | void myFunc(int param[]); |
在数组名字单独出现时,往往被视作一个指针
1 | void myFunc(int* param); |
然而我们可以声明一个参数是对一个数组的引用。设name是一个长度为13的const char[]。
1 | template<typename T> |
T会被推断为const char [13],f的参数是const char (&)[13]。
进一步的,如果想知道一个数组的大小,可以像下面这样
1 | template<typename T, std::size_t N> |
最后给出书中的tips。
Things to Remember
- During template type deduction, arguments that are references are treated as non-references, i.e., their reference-ness is ignored.
- When deducing types for universal reference parameters, lvalue arguments get special treatment.
- When deducing types for by-value parameters, const and/or volatile arguments are treated as non-const and non-volatile.
- During template type deduction, arguments that are array or function names decay to pointers, unless they’re used to initialize references.
存在的疑问: 1,什么是Universal reference? 2,什么样的参数为volatile?
Item 2: auto语句中的类型推断
如何推断下面的代码中 auto 分别代表了什么类型呢?
1 | auto x = 27; |
一般情况下,可以用模板函数中的类型推断来判断auto的结果。如:
1 | template<typename T> // conceptual template for |
根据 Item 1,我们可以将模板的类型推断分为三种:
- Case 1: The type specifier is a pointer or reference, but not a
universal reference.
- Case 2: The type specifier is a universal reference.
- Case 3: The type specifier is neither a pointer nor a reference.
这样,我们可以很自然地推断出下面的例子 auto 具体指代的内容:
1 | auto x = 27; // case 3 (x is neither ptr nor reference) |
同样的,对于数组、函数,也有引用和非引用的区别
1 | const char name[] = "R. N. Briggs"; // name's type is const char[13] |
如果用 auto 的对象是初始化列表,推断的结果也是初始化列表
1 | int x = {27}; |
这也是 auto 和模板类型推断会不同的唯一地方:
1 | auto x = { 11, 23, 9 }; // x's type is |
只能像下面这样用模板推断 initializer_list:
1 | template<typename T> |
另外,虽然C++14允许使用 'auto' 来推断函数返回类型或 lambda 函数参数,但这种用法是使用的模板类型推断。因此,若返回的是初始化列表并不能编译:
1 | auto createInitList() |
Things to Remember
- auto type deduction is usually the same as template type deduction, but auto type deduction assumes that a braced initializer represents a std::initial izer_list, and template type deduction doesn’t.
- auto in a function return type or a lambda parameter implies template type deduction, not auto type deduction.
Item 3: 理解decltype
先来一些 no surprise 的例子
1 | const int i = 0; // decltype(i) is const int |
在C++11中,decltype 主要用于函数返回类型(return type)基于参数类型(parameter types)的情况:
1 | template<typename Container, typename Index> // works, but |
在C++14中可以直接使用 auto 作为返回类型:
1 | template<typename Container, typename Index> // C++14; |
值得注意的是,尽管 c[i] 一般返回的是引用类型,但由于模板类型推断是去引用的,所以下面的代码是错误的。
1 | std::deque<int> d; |
这里,函数 authAndAccess 返回的是一个右值(rvalue),C++ 中给右值赋值是被禁止的。
C++14可以 decltype(auto) 来实现返回与c[i]一致的类型:
1 | template<typename Container, typename Index> // C++14; works, |
decltype(auto)也可以用在变量的声明中:
1 | Widget w; |
decltype 的 surprise 出现在右值容器上。用户可能只想得到临时容器中的拷贝,如:
1 | std::deque<std::string> makeStringDeque(); // factory function |
这时用decltype(auto)就会得到对容器元素的引用,这是并不安全的。为了让 authAndAccess 能同时接受左值和右值,需要用上Universal reference:
1 | template<typename Container, typename Index> // c is now a |
书上还提到了forward,虽然不太了解还是放上来吧:
1 | template<typename Container, typename Index> // final |
变量名是个左值,对一个变量名字使用 decltype,会得到该变量的 declared type。但对于比变量名字更复杂的左值表达式,decltype 总是会得到一个左值引用。比如下面的这个奇怪的例子:
1 | decltype(auto) f1() |
x 是一个类型为 int 的变量名,所以 f1 是 int 类型的。但 (x) 是比名字更复杂的左值表达式,所以 f2 是 int & 类型的。
Things to Remember
- decltype almost always yields the type of a variable or expression without any modifications.
- For lvalue expressions of type T other than names, decltype always reports a type of T&.
- C++14 supports decltype(auto), which, like auto, deduces a type from its initializer, but it performs the type deduction using the decltype rules.
Item 4: 如何观测实际的类型推断
书中给出了三种方法:IDE 自动推断(getting type deduction information as you edit your code),编译中获取(getting it during compilation)和在运行中获取(getting it at runtime)。
Visual Studio 2015可以对相对简单的类型进行推断:
1 | const int theAnswer = 42; |
在代码编译时,若出现 error,会给出相关的信息,这也能得到推断的类型,如:
1 | template<typename T> // declaration only for TD; |
由于TD并没有定义,会出现如下的错误:
error: aggregate 'TD<int> xType' has incomplete
type and cannot be defined error: aggregate 'TD<const int
*> yType' has incomplete type and cannot be defineds
在函数运行时,你也可以使用函数typeid:
1 | std::cout << typeid(x).name() << '\n'; |
然而,这些方法都或多或少有些问题,如VS中的类型推断可能不是很直白:
1 | const std::_Simple_types<std::_Wrap_alloc<std::_Vec_base_types<Widget, |
有个Boost TypeIndex library(http://boost.com ),可以在std::type_info::name 和 IDEs没那么好用的情况下使用。
Things to Remember
- Deduced types can often be seen using IDE editors, compiler error
messages, and the Boost TypeIndex library.
- The results of some tools may be neither helpful nor accurate, so an understanding of C++’s type deduction rules remains essential.