01:理解模板类型推导
- 在模板类型推导中,引用类型参数将被视为非引用类型处理,也就是说其引用性被忽略。 - 在万能引用参数类型推导时,左值参数被特殊处理。 - 值传递形参的类型推导时,其 const 和 volatile 被忽略。 - 在模板类型推导时,数组或者函数类型被转换为指针类型,除非它们用来初始化引用。
- 背景:有时模板类型推导无法一下看出来T是什么类型
1 2 3 4
template <typename T> void f(ParamType param); f(expr); // 比如实参可能是int, const int, const int&
- 类型T的推导不仅取决于expr的类型,也取决于ParamType的形式
- 情况一:ParamType是指针或引用,但不是万能引用
|
|
- 情况二:ParamType是万能引用,因此可能发生引用折叠
|
|
- 情况三:ParamType不是指针,也不是引用,因此视为值传递(实参的const/volatile性质被忽略,因为值进行了复制,形参副本不影响原来的实参)
|
|
- 特殊情况一:传入的实参为数组类型
- 如果模板是情况一:推导出T为数组类型(包含类型和元素数量)
1 2 3
template <typename T> void f(T& param); const char name[] = "zhang"; f(name); // 推导出T=const char[6], f(const char(¶m)[6])
- 应用:比如可以在编译阶段计算数组元素个数:
1 2
template <typename T, std::size_t N> constexpr std::size_t arraySize(T (&)[N]) noexcept {return N;}
- 如果模板是情况三:将数组名视为指针,因此T是指针类型
- 如果模板是情况一:推导出T为数组类型(包含类型和元素数量)
- 特殊情况二:传入的实参为函数类型
- 如果模板是情况一:推导出T为函数引用类型
- 如果模板是情况三:推导出T为函数指针类型
- 参考:
02:理解auto类型推导
- 一般情况下,auto类型推导和模板类型推导完全相同;但是auto类型推导会假定使用
{}
的列表初始化表达式是一个std::initializer_list
,但是模板类型推导不会- 在函数返回值或lambda式形参中使用auto,意思是使用模板类型推导而非使用auto类型推导
- 背景:将一个变量赋值给auto类型变量,auto是什么类型
- 同[[ch01-类型推导#01:理解模板类型推导|01:理解模板类型推导]]中的总体原则:将实参赋值给形参
- 除了一个例外:使用
{}
进行列表初始化 - auto类型推导:
1 2
auto x = {1, 2, 3}; // auto=std::initializer_list<int>,首先推导为std::initializer_list<T>,然后再推断类型T=int auto y{2}; // auto=int
- 模板类型推导:不能直接将
{}
的列表初始化表达式推导为T=std::initializer_list<type>
,1 2 3 4
template <typename T> void f(T param); f({1, 2, 3}); // 报错:直接传入{}列表初始化的实参,模板类型推导失败 template <typename T> void g(std::initializer_list<T> initList); g({1, 2, 3}); // T=int
- 为什么两种行为不同的一个可能解释
1 2 3
template <typename T> void func(T& a, T& b); func(vector<int>{1,2,3}, {1,2,3}); // 左边推导出T=vector<int>, 右边如果推导出T=initializer_list<int>,则左右冲突
- 除了一个例外:使用
- auto可以作为函数返回值类型、lambda式形参类型(C++14)
- 但是原理是模板类型推导,而非auto类型推导
- 参考
03:理解decltype
- 绝大多数情况下,decltype会得到变量或表达式的类型,而不进行修改
- 对于类型为T的左值表达式,除非该表达式只有一个名字,否则decltype总是返回T&
- C++14支持
decltype(auto)
:auto 表示类型需要推导,decltype 表示使用decltype规则进行推导
- 背景:给定一个名字或表达式,
decltype
返回其类型:原来是值/左值/右值,返回值/左值/右值 - 体会auto类型推导和decltype类型推导的区别
- auto类型推导:将变量
rhs
赋值给lhs
,推导出lhs
的类型 - decltype类型推导:返回变量
rhs
的类型
- auto类型推导:将变量
- 使用场景:
- 声明一个函数模板,其返回值类型取决于参数类型
1 2
template <typename Container, typename Index> auto getItem(Container& c, Index i) -> decltpye(c[i]) { return c[i]; } // 返回类型是引用T&
- 如果返回值为auto,使用auto类型推导,返回类型将不是引用
1 2
template <typename Container, typename Index> auto getItem(Container& c, Index i) { return c[i]; } // 返回类型是T
- 可以同时使用auto和
deltype
:auto 表示类型需要推导,decltype 表示使用decltype规则进行推导1 2
template <typename Container, typename Index> decltype(auto) getItem(Container& c, Index i) { return c[i]; } // 返回类型是引用T&
- 优化与完善:为了传入右值的
Container
,使用万能引用,同时使用完美转发1 2
template <typename Container, typename Index> decltype(auto) getItem(Container&& c, Index i) { return std::forward<Container>(c)[i]; } // C++14,或者C++11使用尾置返回类型
- 声明一个函数模板,其返回值类型取决于参数类型
- 一般而言decltype返回的类型都比较直观,除了一种情况:
1 2 3
int x = 0; // decltype(x)=int // decltype((x))=int&
- 参考
04:掌握查看类型推导结果的方法
- 在代码编辑阶段查看类型推导结果:IDE
- 在代码编译阶段查看类型推导结果:查看编译报错
- 在代码运行阶段查看类型推导结果:
typeid
:不同编译期实现不同,无法保证完全可靠,而且类型的引用、const、volatile等性质被忽略- Boost库的模板函数
boost::typeindex::type_id_with_cvr
- 如果类型不包含引用、const、volatile等性质,则
type_id_with_cvr
与typeid
返回相同
- 如果类型不包含引用、const、volatile等性质,则
|
|