[从函数调用推导](https://zh.cppreference.com/w/cpp/language/template_argument_deduction#:~:text=%E4%BB%8E%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8%E6%8E%A8%E5%AF%BC) > [!note] > 沿用手册的措辞,**P** 指代函数形参,**A** 指代函数实参,**T** 为模板类型 推导开始前,对 **P** 和 **A** 进行下列调整 ## 如果 P 不是引用类型 1. 如果 **A** 是数组类型,那么以从数组到指针转换获得的指针类型替换 **A** 2. 如果 **A** 是函数类型,那么以从函数到指针转换获得的指针类型替换 **A** 3. 如果 **A** 是有 cv 限定的类型,那么推导时会忽略顶层 cv 限定符 [cpp insights](https://cppinsights.io/lnk?code=dGVtcGxhdGU8dHlwZW5hbWUgVD4Kdm9pZCBmKFQpIHsKfQoKaW50IGIoaW50KSB7CiAgICByZXR1cm4gMDsKfQoKaW50IG1haW4oKSB7CiAgICBpbnQgYVszXXt9OwogICAgZihhKTsgLy8gZjxpbnQqPgogICAgZihiKTsgLy8gZjxpbnQoKikoaW50KT4KICAgIGNvbnN0IGludCBjID0gMTsKICAgIGYoYyk7IC8vIGY8aW50PgogIAlmKCZjKTsKICAgIHJldHVybiAwOwp9Cg==&insightsOptions=cpp23&std=cpp23&rev=1.0) ```cpp template<typename T> void f(T) { } int b(int) { return 0; } int main() { int a[3]{}; f(a); // f<int*> f(b); // f<int(*)(int)> const int c = 1; f(c); // f<int> return 0; } ``` ## 如果 P 是有 cv 限定的类型 如果 **P** 是有 cv 限定的类型,也就是函数形参有 cv 限定(区别于[[#如果 P 不是引用类型]]前文中第三条,上文为 **A** 有 cv 限定,指的是函数实参),那么推导时会忽略顶层 cv 限定符 意思是如果形参有 const 或者 volatile 修饰,忽略 cv 作为类型,例如 `const int`,T 会推导为 `int` ```cpp template<typename T> void f(const T) { } int main() { f(0); return 0; } ``` 实例化结果为 ```cpp template<> void f<int>(const int) ``` 可以看出 **T** 被推导为 int ## 如果 P 是引用类型 如果 **P** 是引用类型,那么用 **P** 所引用的类型推导,也就是说形参是什么类型的引用,`T`就是什么类型,包含右值引用未使用左值调用的场景 ```cpp template<typename T> void f(T &) { } int main() { int a = 0; auto &r = a; f(r); return 0; } ``` ```cpp template<> void f<int>(int &) //如果是右值引用则推导为 f<int>(int&&) ``` ## 如果 P 是到无 cv 限定模板形参的右值引用(也就是转发引用)且对应函数的调用实参是左值 > [!tip] 这是 std::forward 的行动基础 > 如果 **P** 是到无 cv 限定模板形参的右值引用(也就是转发引用)且对应函数的调用实参是左值,那么将到 A 的左值引用类型用于 A 的位置进行推导 翻译一下就是,如果函数形参 **P** 为`T&&`的形式,且调用函数时的实参 **A** 是左值 ```cpp template<typename T> void f(T &&) { } int main() { int a{0}; f(a); return 0; } ``` **P** -> `T&&` **A** -> `int` 则 T 为 A 的左值引用类型 `int&`,实例化函数如下 ```cpp template<> void f<int &>(int &) ```