模板类型推断机制

auto 推断的基础是模板类型推断机制,但部分特殊情况下,模板推断机制不适用于 auto

模板的形式可以看成如下伪代码:

template<typename T>
void f(ParamType x); //@ ParamType即x的类型

调用可看成:

f(expr);

编译期间,编译器用 expr 推断 TParamType,实际上两者通常不一致,比如:

template<typename T>
void f(const T& x);

int x; //@ 为方便演示,只指定类型不初始化,后续同理
f(x); //@ T被推断为int,ParamType被推断为const int&

T 的类型推断与 exprParamType 相关。

ParamType 不是引用或指针

丢弃 exprtop-level cv 限定符和引用限定符,最后得到的 expr 类型就是 TParamType 的类型。

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]

如果把 ParamTypeT& 改为 const T&,区别只是 ParamType 一定为 top-level constParamType 去掉 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

如果 ParamTypeconst-pointer,和上面实际上是同一个模板,ParamType 多出 top-level constT 不变:

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

如果 ParamTypepointer 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 是左值,TParamType 都推断为左值引用。这有两点非常特殊

  • 这是 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)