今天在一个开源项目中看到一个模板的使用,便想着重新复习这块的内容。
重新翻看以前看过的书籍,发现好多地方都留下了问题,后面也没有再去研究了,再次看到还是一脸懵 。
这一次我结合网上别人的理解和自己动手实践,总算有了一点理解,便把他记录下来,便于以后观看。
template <typename T>
struct identity
{
typedef T type;
};
template <typename T = int>
void func(typename identity<T>::type val, T = 0)
{
//
}
int main()
{
func(123); //T -> int
func(123, 123,0); //T -> double
return 0;
}
在例子中, 通过identity外敷模板禁用了形参val的类型自动推导。但由于func指定了模板参数T的默认类型为int,因此,在func(123)中,func的val参数将为int类型。
而在func(123,123.0)中,由于func的第二个参数123.0为double类型,模板参数T将优先被自动推导为double,因此,此时val的参数将为double类型。
如上所示,书中说identity外敷类禁用了形参的val的类型自动推导,我一直不明白这句话是什么意思。直到我自己动手写了这样一段代码:
#include <iostream>
using namespace std;
template <typename T>
void func(T t1, T t2 = 0)
{
return ;
}
int main()
{
func(123, 123.0);
return 0;
}
我将外敷模板的代码去掉,改成直接使用模板类型。在编译时发现编译不通过,错误如下:
test.cpp: In function ‘int main()’:
test.cpp:13:17: error: no matching function for call to ‘func(int, double)’
13 | func(123, 123.0);
| ^
test.cpp:6:6: note: candidate: ‘template<class T> void func(T, T)’
6 | void func(T t1, T t2 = 0)
| ^~~~
test.cpp:6:6: note: template argument deduction/substitution failed:
test.cpp:13:17: note: deduced conflicting types for parameter ‘T’ (‘int’ and ‘double’)
13 | func(123, 123.0);
| ^
这里报错说没有匹配到函数 func(int, double), 看到这里我想我大概明白上面说的禁用形参自动推导的意思了。
然后我再将外敷类的代码加上去,发现能正常编译和运行。
为了验证书中第一段代码中的最后一句话,我将代码改成了这样:
#include <iostream>
using namespace std;
template <typename T>
struct identity
{
typedef T type;
};
template <typename T>
T func(typename identity<T>::type t1, T t2)
{
cout << "t1:" << t1 << endl;
cout << "t2:" << t2 << endl;
return t1 + t2;
}
int main()
{
cout << func(123, 123.0) << endl;
return 0;
}
发现打印如下:
t1:123
t2:123
246
怎么都是整形的啊,并不是double类型的啊。
模板函数在编译时都会实例化具体的函数,可以通过可执行文件中函数的符号来查看编译器给这个函数的具体化是什么样的。
通过这个命令查找可执行文件的符号表:nm a.out | grep func
0000000000001286 W _Z4funcIdET_N8identityIS0_E4typeES0_
然后再通过这个符号反推出他的函数名:c++filt _Z4funcIdET_N8identityIS0_E4typeES0_
double func<double>(identity<double>::type, double)
确实是double类型的。
这里还涉及到一个函数模板的默认参数的问题。
template <typename T = int>
void func(typename identity<T>::type val, T = 0)
在这段代码中, T = 0,我以前一直不理解他是什么意思,今天我才明白他就是一个函数的默认参数,只不过这个参数的类型是一个模板参数类型。
总结:
1、在使用模板函数时,如果不指定模板参数类型,那么编译器就会帮我们自动推导。
2、当我们将模板函数的形参类型再进行包装(比如使用外敷类)时,就是禁用掉编译器的自动类型推导,在调用时就需要指明参数类型。
3、当函数中的参数不使用或者在头文件中声明时可以不写参数名(普通函数时是知道的,模板函数一时没反应过来 )。
书中还有一些总结性的话,一并写在这里了:
1、当所有模板参数都有默认参数时,函数模板的调用如同一个普通函数。对于类模板而言,哪怕所有参数都有默认参数,在使用时也必须在模板名后跟随"<>"来实例化。
2、函数模板的默认模板参数不必要写在参数表最后。(我的理解:模板参数的顺序并不等于在函数参数中使用的顺序,所以不必要写在最后)书上这里还有一句话,在调用函数模板时,显示指定的模板参数,其填充顺序是从右往左(这点我测试了,不是这样的。指定的模板参数是从左往右填充的)。