函数模板的特化和重载

不同于类模板,可以具有显式特化和局部特化(partial specializations),函数模板没有”局部特化”的概念,只有显式特化和重载。

函数模板在C++标准中的描述:

A function template defines an unbounded set of related functions.

函数模板特化和函数重载的区别是什么?

**[TC++PL4th]**How does a specialization differ from overloading? From a technical point of view, they differ because individual functions take part in overloading whereas only the primary template takes part in specialization.

函数模板的特化和普通的函数重载略有不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <typeinfo>

template<typename T>
void func(T){
cout<<typeid(T).name()<<endl;
}

template<>
void func(int){
cout<<typeid(int).name()<<endl;
}

void func(int){
cout<<"type is int."<<endl;
}

int main()
{
int ival=123;
func(ival);
}
// output: type is int.

这里void func(int)是函数重载,而不是函数模板特化(不过从本质来说模板特化出来的函数也是重载函数)。
注意下面的问题:

1
2
3
4
int ival=123;
func(ival); // type is int.
// 调用的是不同的函数
func<int>(ival); // i

从中间代码(LLVM-IR)的角度来看更直观一些:

1
2
3
4
5
6
%1 = alloca i32, align 4
store i32 123, i32* %1, align 4
%2 = load i32, i32* %1, align 4
call void @_Z4funci(i32 %2)
%3 = load i32, i32* %1, align 4
call void @_Z4funcIiEvT_(i32 %3)

原因在于func<int>(ival)显式地通过函数模板得到一个重载的func实例,而和void func(int)调用的函数不同是由于普通的函数和函数模板具有两种不同的签名规则:

  • <function> name, parameter type list (8.3.5), and enclosing namespace (if any)
  • <function template> name, parameter type list (8.3.5), enclosing namespace (if any), return type, and template parameter list
  • <function template specialization> signature of the template of which it is a specialization and its template arguments (whether explicitly specified or deduced)

C++标准中给出了更直接的描述:

A non-template function is not related to a function template (i.e., it is never considered to be a specialization), even if it has the same name and type as a potentially generated function template specialization.
That is, declarations of non-template functions do not merely guide overload resolution of function template specializations with the same name. If such a non-template function is odr-used (3.2) in a program, it must be defined; it will not be implicitly instantiated using the function template definition.

关于重载决议的描述:

A function template can be overloaded either by (non-template) functions of its name or by (other) function templates of the same name.

函数的重载决议的最佳可行函数(Best viable function)具有一个非常复杂的规则,在本篇文章中按下不表。
函数特化的功能有限,在对无参函数进行匹配时有用:

1
2
3
4
5
6
7
8
template <typename T>
void func(){}
void func<int>(){/*int*/}
void func<double>(){/*double*/}

// call
func<int>();
func<double>();

而且,两个不同的函数模板有可能会特化出相同的类型:

It is possible to overload function templates so that two different function template specializations have the same type.

1
2
3
4
5
6
// file1.cc
template<class T>
void f(T*);
void g(int* p) {
f(p); // calls f<int>(int*)
}

以及:

1
2
3
4
5
6
// file2.cc
template<class T>
void f(T);
void h(int* p) {
f(p); // calls f<int*>(int*)
}

这样的特化并不违反ODR:

Such specializations are distinct functions and do not violate the one definition rule (3.2).

原因就是前面提到的函数模板的签名规则是包含parameter type list的。
所以,void f(T*)void func(T)是具有不同签名的函数:

  • void func(T*)的参数类型列表为T,函数参数为T*
  • void func(T)的参数类型列表为T*,参数类型也为T*

可以看一下上面代码的IR代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// file1.cc
; Function Attrs: uwtable
define void @_Z1gPi(i32*) #0 {
%2 = alloca i32*, align 8
store i32* %0, i32** %2, align 8
%3 = load i32*, i32** %2, align 8
call void @_Z1fIiEvPT_(i32* %3)
ret void
}

// file2.cc
; Function Attrs: uwtable
define void @_Z1gPi(i32*) #0 {
%2 = alloca i32*, align 8
store i32* %0, i32** %2, align 8
%3 = load i32*, i32** %2, align 8
call void @_Z1fIPiEvT_(i32* %3)
ret void
}

用直观的diff来看一下:

可以看到,虽然两个不同的函数模板可以特化出接收相同参数的函数类型,但是本质上他们还是不一样的。

全文完,若有不足之处请评论指正。

微信扫描二维码,关注我的公众号。

本文标题:函数模板的特化和重载
文章作者:查利鹏
发布时间:2017年05月04日 00时20分
本文字数:本文一共有2k字
原始链接:https://imzlp.com/posts/10380/
许可协议: CC BY-NC-SA 4.0
文章禁止全文转载,摘要转发请保留原文链接及作者信息,谢谢!
您的捐赠将鼓励我继续创作!