Specialization and overloading of C++ function templates

C++函数模板的特化和重载

Unlike class templates, which can have explicit specializations and partial specializations, function templates do not have the concept of “partial specializations”; they only have explicit specializations and overloads.

Description of function templates in the C++ standard:

A function template defines an unbounded set of related functions.

What is the difference between specialization and function overloading?

**[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.

The specialization of function templates is slightly different from ordinary function overloading:

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.

Here, void func(int) is function overloading, not function template specialization (although function specialization functions are also overloaded functions in essence). Note the following issues:

1
2
3
4
int ival=123;
func(ival); // type is int.
// Calls different functions
func<int>(ival); // i

From an intermediate code (LLVM-IR) perspective, this is more intuitive:

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)

The reason is that func<int>(ival) explicitly generates an overloaded func instance through the function template, while the function called with void func(int) is different due to the fact that ordinary functions and function templates have two different signature rules:

  • <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)

The C++ standard provides a more direct description:

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.

About overload resolution:

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

The best viable function resolution for function overloading has a very complex rule, which will not be discussed in this article.
Function specialization has limited functionality and is useful for matching zero-argument functions:

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>();

Moreover, two different function templates can specialize to the same type:

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*)
}

And:

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

Such specializations do not violate ODR:

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

The reason is that the signature rules of function templates include the parameter type list.
Thus, void f(T*) and void func(T) are functions with different signatures:

  • The parameter type list for void func(T*) is T, and the function parameter is T*
  • The parameter type list for void func(T) is T*, and the parameter type is also T*

You can look at the IR code of the above:

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
}

Using an intuitive diff view:

It can be seen that although two different function templates can specialize to functions that receive the same parameters, essentially they are still different.

The article is finished. If you have any questions, please comment and communicate.

Scan the QR code on WeChat and follow me.

Title:Specialization and overloading of C++ function templates
Author:LIPENGZHA
Publish Date:2017/05/04 00:20
Word Count:3k Words
Link:https://en.imzlp.com/posts/10380/
License: CC BY-NC-SA 4.0
Reprinting of the full article is prohibited.
Your donation will encourage me to keep creating!