模板类型推断机制¶
auto 推断的基础是模板类型推断机制,但部分特殊情况下,模板推断机制不适用于 auto 。
模板的形式可以看成如下伪代码:
template<typename T>
void f(ParamType x); //@ ParamType即x的类型
调用可看成:
f(expr);
编译期间,编译器用 expr 推断 T 和 ParamType,实际上两者通常不一致,比如:
template<typename T>
void f(const T& x);
int x; //@ 为方便演示,只指定类型不初始化,后续同理
f(x); //@ T被推断为int,ParamType被推断为const int&
T 的类型推断与 expr 和 ParamType 相关。
ParamType 不是引用或指针¶
丢弃 expr 的 top-level cv 限定符和引用限定符,最后得到的 expr 类型就是 T 和 ParamType 的类型。
template<typename T>
void f(T x);
int a;
const int b;
const int& c;
int* p1;
const int* p2;
int* const p3;
const int* const p4;
char s1[] = "downdemo";
const char s2[] = "downdemo";
//@ 以下情况T和ParamType都是int
f(a);
f(b);
f(c);
//@ 指针类型丢弃的是top-level const(即指针本身的const)
//@ low-level const(即所指对象的const)会保留
f(p1); //@ T和ParamType都是int*
f(p2); //@ T和ParamType都是const int*
f(p3); //@ T和ParamType都是int*
f(p4); //@ T和ParamType都是const int*
//@ char数组会退化为指针
f(s1); //@ T和ParamType都是char*
f(s2); //@ T和ParamType都是const char*
ParamType 是引用类型¶
如果 expr 的类型是引用,保留 cv 限定符,ParamType 一定是左值引用类型,ParamType 去掉引用符就是 T 的类型,即 T 一定不是引用类型。
template<typename T>
void f(T& x);
int a;
int& b;
int&& c;
const int d;
const int& e;
int* p1;
const int* p2;
int* const p3;
const int* const p4;
char s1[] = "downdemo";
const char s2[] = "downdemo";
f(a); //@ ParamType是int&,T是int
f(b); //@ ParamType是int&,T是int
f(c); //@ ParamType是int&,T是int
f(d); //@ ParamType是const int&,T是const int
f(e); //@ ParamType是const int&,T是const int
//@ 因为top-level const和low-level const都保留,对于指针只要记住const的情况和实参类型一样
f(p1); //@ ParamType是int* &,T是int*
f(p2); //@ ParamType是const int* &,T是const int*
f(p3); //@ ParamType是int* const&,T是int* const
f(p4); //@ ParamType是const int* const &,T是const int* const
//@ 数组类型对于T&的情况比较特殊,不会退化到指针
f(s1); //@ ParamType是char(&)[9],T是char[9]
f(s2); //@ ParamType是const char(&)[9],T是const char[9]
如果把 ParamType 从 T& 改为 const T&,区别只是 ParamType 一定为 top-level const,ParamType 去掉 top-level const 和引用符就是 T 的类型,即 T 一定不为 top-level const 引用类型:
template<typename T>
void f(const T& x);
int a;
int& b;
int&& c;
const int d;
const int& e;
int* p1;
const int* p2;
int* const p3;
const int* const p4;
char s1[] = "downdemo";
const char s2[] = "downdemo";
//@ 以下情况ParamType都是const int&,T都是int
f(a);
f(b);
f(c);
f(d);
f(e);
//@ 数组类型类似
f(s1); //@ ParamType是const char(&)[9],T是char[9]
f(s2); //@ ParamType是const char(&)[9],T是char[9]
//@ 对于指针只要记住,T的指针符后一定无const
f(p1); //@ ParamType是int* const &,T是int*
f(p2); //@ ParamType是const int* const &,T是const int*
f(p3); //@ ParamType是int* const&,T是int*
f(p4); //@ ParamType是const int* const &,T是const int*
对应数组类型的模板参数类型应声明为 T (&) [N],即数组类型 T[N] 的引用:
template<typename T, std::size_t N>
constexpr std::size_t f(T (&) [N]) noexcept
{
return N;
}
const char s[] = "downdemo";
int a[f(s)]; //@ int a[9]
ParamType 是指针类型¶
与情形2类似,ParamType 一定是 non-const 指针(传参时忽略 top-level const)类型,去掉指针符就是 T 的类型,即 T 一定不为指针类型:
template<typename T>
void f(T* x);
int a;
const int b;
int* p1;
const int* p2;
int* const p3; //@ 传参时与p1类型一致
const int* const p4; //@ 传参时与p2类型一致
char s1[] = "downdemo";
const char s2[] = "downdemo";
f(&a); //@ ParamType是int*,T是int
f(&b); //@ ParamType是const int*,T是const int
f(p1); //@ ParamType是int*,T是int
f(p2); //@ ParamType是const int*,T是const int
f(p3); //@ ParamType是int*,T是int
f(p4); //@ ParamType是const int*,T是const int
//@ 数组类型会转为指针类型
f(s1); //@ ParamType是char*,T是char
f(s2); //@ ParamType是const char*,T是const char
如果 ParamType 是 const-pointer,和上面实际上是同一个模板,ParamType 多出 top-level const,T 不变:
template<typename T>
void f(T* const x);
int a;
const int b;
int* p1; //@ 传参时与p3类型一致
const int* p2; //@ 传参时与p4类型一致
int* const p3;
const int* const p4;
char s1[] = "downdemo";
const char s2[] = "downdemo";
f(&a); //@ ParamType是int* const,T是int
f(&b); //@ ParamType是const int* const,T是const int
f(p1); //@ ParamType是int* const,T是int
f(p2); //@ ParamType是const int* const,T是const int
f(p3); //@ ParamType是int* const,T是int
f(p4); //@ ParamType是const int* const,T是const int
f(s1); //@ ParamType是char* const,T是char
f(s2); //@ ParamType是const char* const,T是const char
如果 ParamType 是 pointer to const,则只有一种结果,T 一定是不带 const 的非指针类型:
template<typename T>
void f(const T* x);
template<typename T>
void g(const T* const x);
int a;
const int b;
int* p1;
const int* p2;
int* const p3;
const int* const p4;
char s1[] = "downdemo";
const char s2[] = "downdemo";
//@ 以下情况ParamType都是const int*,T都是int
f(&a);
f(&b);
f(p1);
f(p2);
f(p3);
f(p4);
//@ 以下情况ParamType都是const int* const,T都是int
g(&a);
g(&b);
g(p1);
g(p2);
g(p3);
g(p4);
//@ 以下情况ParamType都是const char*,T都是char
f(s1);
f(s2);
g(s1);
g(s2);
ParamType 是转发引用¶
如果 expr 是左值,T 和 ParamType 都推断为左值引用。这有两点非常特殊
- 这是
T被推断为引用的唯一情形 ParamType使用右值引用语法,却被推断为左值引用
如果 expr 是右值,则 ParamType 推断为右值引用类型,去掉 && 就是 T 的类型,即 T 一定不为引用类型。
template<typename T>
void f(T&& x);
int a;
const int b;
const int& c;
int&& d = 1; //@ d是右值引用,也是左值,右值引用是只能绑定右值的引用而不是右值
char s1[] = "downdemo";
const char s2[] = "downdemo";
f(a); //@ ParamType和T都是int&
f(b); //@ ParamType和T都是const int&
f(c); //@ ParamType和T都是const int&
f(d); //@ ParamType和T都是const int&
f(1); //@ ParamType是int&&,T是int
f(s1); //@ ParamType和T都是char(&)[9]
f(s2); //@ ParamType和T都是const char(&)[9]
expr 是函数名¶
template<typename T> void f1(T x);
template<typename T> void f2(T& x);
template<typename T> void f3(T&& x);
void g(int);
f1(g); //@ T和ParamType都是void(*)(int)
f2(g); //@ ParamType是void(&)(int),T是void()(int)
f3(g); //@ T和ParamType都是void(&)(int)