模板类型推导

前言

类型推导使得程序员从冗杂的类型名拼写上解放出来,本篇文章将详细介绍模型类型推导的规则。

模板类型推导的规则

考虑如下函数模版:

1
2
3
template<typename T>
void f(ParamType param);
f(expr);

在编译期间,编译器使用expr进行两个类型的推导:TParamType。其中,ParamType通常包含一些修饰,从而通常与T是不同的类型。T的类型推导不仅取决于expr的类型,也被ParamType决定,可以将其分为三种情况:

  • ParamType是一个指针或引用,但不是通用引用
  • ParamType是一个通用引用
  • ParamType既不是指针也不是引用
    下面分别介绍三种情况下的类型推导规则

情况一:ParamType是一个指针或引用,但不是通用引用

在该情况下,类型推导按照如下规则进行:

  • 如果expr的类型是一个引用,忽略引用部分
  • expr的类型与ParamType进行模式匹配决定T
    对于如下模板示例:
1
2
template <typename T>
void f(T& param)

则类型推导结果如下:

1
2
3
4
5
6
7
int x = 27;          // x是int
const int cx = x; // cx是const int
const int& rx = x; // rx是指向const int变量x的引用

f(x); // T是int,param类型是int&
f(cx); // T是const int,param是const int&
f(rx); // T是const int,param是const int&

从上述结果中可以看出,const对象作为实参传递给T&形参时,const会被保留为T的一部分

如果param是一个指针,对应的情况与引用基本一致:

1
2
3
4
5
6
7
8
template <typename T>
void f(T* param)

int x = 27; // x是int类型
const int *px = &x; // px是指向const int变量的指针

f(&x); // T是int,param是int*
f(px); // T是const int,param是const int*

情况二:ParamType是一个通用引用

该情况的形式如下:

1
2
template <typename T>
void f(T&& param)

对应的类型推导规则有以下几点:

  • 如果expr是左值,TParamType都会被推导为左值引用。
  • 如果expr是右值,使用情况一的推导规则(T保留const属性和基本类型,param是右值引用)
    下面给出对应的示例:
1
2
3
4
5
6
7
8
9
10
11
template <typename T>
void f(T&& param)

int x = 27;
const int cx = x;
const int& rx = cx;

f(x); // x是左值,T是int&,param也是int&
f(cx); // cx是左值,所以T是const int&,param也是const int&
f(rx); // rx是左值,所以T是const int&,param也是const int&
f(27); // 27是右值,所以T是int,param类型是int&&

情况三:ParamType既不是指针也不是引用

该情况下通过传值的方式处理:

1
2
template <typename T>
void f(T param)

对应如下规则:

  • 如果expr的类型是一个引用,忽略这个引用部分
  • 如果忽略expr的引用性之后,expr是一个const,那么再忽略const。如果是volatile,也忽略volatile
    下面给出对应的示例:
1
2
3
4
5
6
7
int x = 27;
const int cx = x;
const int& rx = cx;

f(x); // T和param的类型都是int
f(cx); // T和param的类型都是int
f(rx); // T和param的类型都是int

该情况下,param是传入对象的拷贝,不影响传入对象。

一个例外情况是传入的是指向常量的常量指针,此时指针指向数据的常量性会被保留,而指针本身的常量性会被忽略:

1
2
3
4
5
template <typename T>
void f(T param)

const char* const ptr = "Fun with pointers"; // ptr是指向常量的常量指针
f(ptr); // param是const char*

数组实参

我们知道数组在某些上下文中会退化为指向它的第一个元素的指针,给出如下示例:

1
2
const char name[] = "J. P. Briggs";
const char * ptrToName = name;

在该示例中,name的类型为const char[13],而ptrToName的类型为const char*,但编译器允许数组退化为指针。

将数组作为实参传递给模版:

1
2
3
4
template<typename T>
void f(T param); //传值形参的模板

f(name); // T被推导为const char*

有没有办法让T推导为数组,而不是指针呢?答案是使用传引用模板:

1
2
3
4
template <typename T>
void f(T& param);

f(name);

此时T会被推导为真正的数组const char[13],形参类型为const char (&)char[13]

我们可以根据这一性质创建一个模板函数推导数组的大小:

1
2
3
4
5
6
7
8
template <typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)[N]) noexcept {
return N;
}

int keyVals[] = {1, 3, 7, 9, 11, 22, 35};

int mappedVals[arraySize(keyVals)]; // 使用一个数组的大小声明另一个数组

函数实参

函数类型也会退化为函数指针,对于数组类型的推导可以应用到函数类型退化到函数指针上来:

1
2
3
4
5
6
7
8
9
10
11
12
13
void someFunc(int, double);         //someFunc是一个函数,
//类型是void(int, double)

template<typename T>
void f1(T param); //传值给f1

template<typename T>
void f2(T & param); //传引用给f2

f1(someFunc); //param被推导为指向函数的指针,
//类型是void(*)(int, double)
f2(someFunc); //param被推导为指向函数的引用,
//类型是void(&)(int, double)

参考

类型推导


模板类型推导
https://delta0406.github.io/2025/06/14/技术/语言/CPP/模板类型推导/
作者
执妄
发布于
2025年6月14日
许可协议