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是指针或引用,但不是万能引用
1
2
3
template <typename T> void f(T& param);
template <typename T> void f(const T& param);
template <typename T> void f(T* param);
  • 情况二:ParamType是万能引用,因此可能发生引用折叠
1
template <typename T> void f(T&& param);
  • 情况三:ParamType不是指针,也不是引用,因此视为值传递(实参的const/volatile性质被忽略,因为值进行了复制,形参副本不影响原来的实参)
1
template <typename T> void f(T param);
  • 特殊情况一:传入的实参为数组类型
    • 如果模板是情况一:推导出T为数组类型(包含类型和元素数量)
      1
      2
      3
      
      template <typename T> void f(T& param);
      const char name[] = "zhang";
      f(name); // 推导出T=const char[6], f(const char(&param)[6])
      
      • 应用:比如可以在编译阶段计算数组元素个数:
      1
      2
      
      template <typename T, std::size_t N>
      constexpr std::size_t arraySize(T (&)[N]) noexcept {return N;}
      
    • 如果模板是情况三:将数组名视为指针,因此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的类型
  • 使用场景:
    • 声明一个函数模板,其返回值类型取决于参数类型
      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_cvrtypeid返回相同
1
2
3
4
5
6
7
8
#include <boost/type_index.hpp>
template <typename T>
void f(const T& param){
    using std::cout;
    using boost::typeindex::type_id_with_cvr; // c:const, v:volatile, r:reference
    cout<<type_id_with_cvr<T>().pretty_name()<<"\n";
    cout<<type_id_with_cvr<decltype(param)>().pretty_name()<<"\n";
}