理解模板类别的推导
绪
函数模板伪代码如下:
template<typename T>
void f(ParamType param);
一次调用的形如f(expr);
,编译器用 expr 来推导两个类型: T
和 ParamType
。 这些类型经常是不一样的,因为 ParamType
一般带有限定符,如 const
或引用。
T 的类型推导不仅仅依赖于expr
的类型, 还依赖于 ParamType
的格式。具体需要从三个方面讨论:
如何查看模板类型参数导出的类型的方法
方式1:
见如下连接
How can I see the type deduced for a template type parameter?
方式2:
点击网址打开Compiler查看生成的代码,如下
ParamType 是个指针或引用,但不是万能引用
类型推导步骤为:
- 若
expr
具有引用类型,先将引用部分忽略 - 之后,对
expr
的类别和ParamType
的类别进行匹配来决定T的类别。
情形1 f(T& a)
方式1
使用删除的模板函数
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
//情形1
template<typename T>
void f(T& a)=delete ;
int main() {
int x = 22;
const int lx = x;
const int &rx = x;
int&& rx1 = 27;
const int*px = &x;
int*px1 = &x;
f(x);
f(lx);
f(rx);
f(rx1);
f(px);
f(px1);
f(*px);
f(*px1);
}
编译输出如下:
<source>: In function 'int main()':
<source>:17:6: error: use of deleted function 'void f(T&) [with T = int]'
17 | f(x);
| ~^~~
<source>:8:6: note: declared here
8 | void f(T& a)=delete ;
| ^
<source>:18:6: error: use of deleted function 'void f(T&) [with T = const int]'
18 | f(lx);
| ~^~~~
<source>:8:6: note: declared here
8 | void f(T& a)=delete ;
| ^
<source>:19:6: error: use of deleted function 'void f(T&) [with T = const int]'
19 | f(rx);
| ~^~~~
<source>:8:6: note: declared here
8 | void f(T& a)=delete ;
| ^
<source>:20:6: error: use of deleted function 'void f(T&) [with T = int]'
20 | f(rx1);
| ~^~~~~
<source>:8:6: note: declared here
8 | void f(T& a)=delete ;
| ^
<source>:21:6: error: use of deleted function 'void f(T&) [with T = const int*]'
21 | f(px);
| ~^~~~
<source>:8:6: note: declared here
8 | void f(T& a)=delete ;
| ^
<source>:22:6: error: use of deleted function 'void f(T&) [with T = int*]'
22 | f(px1);
| ~^~~~~
<source>:8:6: note: declared here
8 | void f(T& a)=delete ;
| ^
<source>:23:6: error: use of deleted function 'void f(T&) [with T = const int]'
23 | f(*px);
| ~^~~~~
<source>:8:6: note: declared here
8 | void f(T& a)=delete ;
| ^
<source>:24:6: error: use of deleted function 'void f(T&) [with T = int]'
24 | f(*px1);
| ~^~~~~~
<source>:8:6: note: declared here
8 | void f(T& a)=delete ;
| ^
从输出的结果可看出推导的类型
方式2
// Compile with /W4
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
template<typename T>
void f(T& a){
cout<<__func__<<" "<<a<<endl;
} ;
int main() {
int x = 22;
const int lx = x;
const int &rx = x;
int&& rx1 = 27;
const int*px = &x;
int*px1 = &x;
//void f<int>(int&)
f(x); //T int; ParamType int&
//void f<int const>(int const&)
f(lx); //T const int; ParamType const int&
//void f<int const>(int const&)
f(rx); //T const int; ParamType const int&
// void f<int>(int&)
f(rx1); //T int; ParamType int&
//void f<int const*>(int const*&)
f(px); //T const int*; ParamType const int *&
//void f<int*>(int*&)
f(px1); //T int*; ParamType int*&
//void f<int const>(int const&)
f(*px); //T const int; ParamType const int&
//void f<int>(int&)
f(*px1); //T int; ParamType int&
}
生成的汇编代码如下:
...
mov rdi, rax
call void f<int>(int&)
lea rax, [rbp-24]
mov rdi, rax
call void f<int const>(int const&)
mov rax, QWORD PTR [rbp-8]
mov rdi, rax
call void f<int const>(int const&)
mov rax, QWORD PTR [rbp-16]
mov rdi, rax
call void f<int>(int&)
lea rax, [rbp-40]
mov rdi, rax
call void f<int const*>(int const*&)
lea rax, [rbp-48]
mov rdi, rax
call void f<int*>(int*&)
mov rax, QWORD PTR [rbp-40]
mov rdi, rax
call void f<int const>(int const&)
mov rax, QWORD PTR [rbp-48]
mov rdi, rax
call void f<int>(int&)
mov eax, 0
leave
ret
...
输出结果为
f 22
f 22
f 22
f 27
f 0x7ffdfd4ae16c
f 0x7ffdfd4ae16c
f 22
f 22
结论如下:
expr | f(expr) | T | ParamType | instance |
---|---|---|---|---|
int x | f(x) | int | int& | void f<int>(int&) |
const int lx | f(lx) | const int | const int& | void f<int const>(int const&) |
const int &rx | f(rx) | const int | const int& | void f<int const>(int const&) |
int&& rx1 | f(rx1) | int | int& | void f<int>(int&) |
const int*px | f(px) | const int* | const int* & | void f<int const*>(int const*&) |
int*px1 | f(px1) | int * | int* & | void f<int*>(int*&) |
const int*px | f(*px) | const int | const int& | void f<int const>(int const&) |
int*px1 | f(*px1) | int | int& | void f<int>(int&) |
情形2 f(const T& a)
代码
// Compile with /W4
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
template<typename T>
void f(const T& a){
cout<<__func__<<" "<<a<<endl;
} ;
int main() {
int x = 22;
const int lx = x;
const int &rx = x;
int&& rx1 = 27;
const int*px = &x;
int*px1 = &x;
f(x);
f(lx);
f(rx);
f(rx1);
f(px);
f(px1);
f(*px);
f(*px1);
}
生成汇编代码如下:
...
call void f<int>(int const&)
...
call void f<int>(int const&)
...
call void f<int>(int const&)
...
call void f<int>(int const&)
...
call void f<int const*>(int const* const&)
...
call void f<int*>(int* const&)
...
call void f<int>(int const&)
..
call void f<int>(int const&)
结论
expr | f(expr) | T | ParamType | instance |
---|---|---|---|---|
int x | f(x) | int | const int& | void f<int>(int const&) |
const int lx | f(lx) | int | const int& | void f<int>(int const&) |
const int &rx | f(rx) | int | const int& | void f<int>(int const&) |
int&& rx1 | f(rx1) | int | const int& | void f<int>(int const &) |
const int*px | f(px) | const int* | const int* & | void f<int const*>(int const* const&) |
int*px1 | f(px1) | int * | int* & | void f<int*>(int* const &) |
const int*px | f(*px) | int | const int& | void f<int>(int const&) |
int*px1 | f(*px1) | int | const int& | void f<int>(int const&) |
情形3 f(T* a)
代码
// Compile with /W4
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
template<typename T>
void f(T* a){
cout<<__func__<<" "<<*a<<endl;
} ;
// template<typename T>
// void f(T*& a)=delete;
int main() {
int x = 22;
const int lx = x;
const int &rx = x;
int&& rx1 = 27;
const int*px = &x;
int*px1 = &x;
f(&x);
f(&lx);
f(&rx);
f(&rx1);
f(px);
f(px1);
// f(*px); // no matching function for call to 'f(const int&)'
// f(*px1); //error: no matching function for call to 'f(int&)'
}
生成的汇编代码如下:
call void f<int>(int*)
...
call void f<int const>(int const*)
...
call void f<int const>(int const*)
...
call void f<int>(int*)
...
call void f<int const>(int const*)
...
call void f<int>(int*)
...
结论
expr | f(expr) | T | ParamType | instance |
---|---|---|---|---|
int x | f(x) | int | int&* | void f<int>(int *) |
const int lx | f(lx) | const int | const int* | void f<int>(int const*) |
const int &rx | f(rx) | int const | const int* | void f<int>(int const*) |
int&& rx1 | f(rx1) | int | int* | void f<int>(int*) |
const int*px | f(px) | const int | const int* | void f<int const>(int const*) |
int*px1 | f(px1) | int * | int* | void f<int*>(int*) |
情形4 f(const T* a)
与情形2类似不做讨论
ParamType 是个万能引用
推导规则会区分左值和右值
expr
是左值,T
和ParamType
都会被推导为左值expr
是右值,则应用《 ParamType 是个指针或引用,但不是万能引用》规则。
// Compile with /W4
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
template<typename T>
void f4(T&& a)=delete ;
int main() {
int x = 22;
const int lx = x;
const int &rx = x;
const int && yy = 27;
f4(x); // 左值 :T int&; ParamType int &
f4(lx);// 左值 :T const int&; ParamType const int&
f4(rx);// 左值 :T const int&; ParamType const int*
f4(26);// 右值 :T int; ParamType int &&
f4(yy);// 左值 :T const int&; ParamType const int&
}
编译结果如下
<source>: In function 'int main()':
<source>:18:7: error: use of deleted function 'void f4(T&&) [with T = int&]'
...
<source>:19:7: error: use of deleted function 'void f4(T&&) [with T = const int&]'
...
<source>:20:7: error: use of deleted function 'void f4(T&&) [with T = const int&]'
...
<source>:21:7: error: use of deleted function 'void f4(T&&) [with T = int]'
...
<source>:22:7: error: use of deleted function 'void f4(T&&) [with T = const int&]'
...
ParamType 既非指针也非引用
template<typename T>
void f(T a)=delete ;
这种情况就是按值进行传递,无论下述代码中,传入什么,a都会是它的一个副本,也就是说是一个全新的对象。
推到规则如下:
- 若
expr
是引用类别,那么忽略掉 - 忽略掉expr的引用特性后,若expr是const对象也忽略,若expr是个volatile对象同样忽略。
// Compile with /W4
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
template<typename T>
void f(T a)=delete ;
int main() {
int x = 22;
const int lx = x;
const int &rx = x;
const int && yy = 27;
const char*const ptr = "xi_men_chui_xue";
f(x);
f(lx);
f(rx);
f(26);
f(yy);
f(ptr); //T 为const char *
}
<source>:20:6: error: use of deleted function 'void f(T) [with T = int]'
...
<source>:21:6: error: use of deleted function 'void f(T) [with T = int]'
...
<source>:22:6: error: use of deleted function 'void f(T) [with T = int]'
...
<source>:23:6: error: use of deleted function 'void f(T) [with T = int]'
...
<source>:24:6: error: use of deleted function 'void f(T) [with T = int]'
...
<source>:25:6: error: use of deleted function 'void f(T) [with T = const char*]'
...
注: 在推导过程中,
ptr
所指内容的常量性会得到保留,但是其自身的常量性被忽略
数组实参
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
template<typename T>
void f(T a){} ;
template<typename T>
void f1(T& a){} ;
template<typename T,std::size_t N>
constexpr std::size_t arraySize(T (&)[N])noexcept{
return N;
}
int main() {
const char name[]= "xi men chui da xue";
f(name);
f1(name);
arraySize(name);
}
生成的汇编代码如下:
...
call void f<char const*>(char const*)
...
call void f1<char const [19]>(char const (&) [19])
...
call unsigned long arraySize<char const, 19ul>(char const (&) [19ul])
...
结论:
- ParamType非指针非引用 即按值传递,按值传递给函数模板的数组类别会被推导程指针类别。
- 按引用传递给函数模板的会被推导程数组类别,这个类别会包含数组的尺寸
函数实参
同数组实参类似。
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
template<typename T>
void f(T a){} ;
template<typename T>
void f1(T& a){} ;
void function(){
}
int main() {
const char name[]= "xi men chui da xue";
f(function);//退化为指针
f1(function);//推导为函数引用
}
汇编代码如下:
mov QWORD PTR [rbp-32], rax
mov QWORD PTR [rbp-24], rdx
mov DWORD PTR [rbp-17], 6649208
mov edi, OFFSET FLAT:function()
call void f<void (*)()>(void (*)())
mov edi, OFFSET FLAT:function()
call void f1<void ()>(void (&)())
mov eax, 0