[C++] function template (it is enough to read this article)

function template

The meaning of the existence of function templates

In real life, in order to improve work efficiency, we usually use some templates to complete some things, such as: year-end summary ppt, we can use some ready-made ppt templates to write, usually we only need to fill in the content , instead of going from 0 to 1 to make all aspects of the ppt, we only need to focus on the core (for us, the content of the year-end summary is the most critical).

In C++, the same is true for templates. Its appearance greatly improves the reusability of the code, making our code more versatile and flexible! One of the most important features of C++ is the reuse of code. In order to achieve code reuse, the code must be universal. Generic code is not affected by data types and can automatically adapt to changes in data types. This type of programming is called parameterized programming.

Template is a tool for C++ to support parameterized programming, through which parameterized polymorphism can be realized. The so-called parametric polymorphism refers to: parameterize the types of objects handled by the program, so that a program can handle multiple different types of objects. Simple understanding, the template is to make the code more versatile.

With the above understanding, it is much easier to understand function templates and class templates below.

Why do we need function templates when we have function overloading?

To talk about function templates, we must first talk about function overloading. I believe everyone knows that function overloading usually performs similar operations on different data types.

In many cases, an algorithm can actually handle multiple data types. But when implementing an algorithm with a function, even if it is designed as an overloaded function, it is only

Using the same function name, the function body is still defined separately. The following is an example of function overloading for absolute value, which is convenient for everyone to understand.

int abs(int x){
    return x<0?-x:x;
}
double abs(double x){
    return x<0?-x:x;
}

It can be seen that the function and function body of these two functions are exactly the same, but in order to realize the absolute value operation of data of different data types, we have to write two functions with the same name to realize the operation.

It would be great if you could write a piece of general code applicable to many different data types, which would greatly improve the reusability of the code, thereby improving the efficiency of software development. Therefore, such a tool was designed, that is, the function template. The programmer only needs to write the function template once, and then based on the parameter type provided when calling the function, the C++ compiler will automatically generate the corresponding function to correctly handle the data of this type. .

Definition of function template

The definition of a function template is as follows:

template<typename 类型名>
类型名  函数名(参数列表)
{
函数体的定义;
}

The above typename identifier is a new identifier later, which is easier to understand. The previous C++ version can also use the class identifier to replace the typename here. This identifier indicates that a type parameter can be received. These type parameters represent types. It is a data type predefined by the system, or it can be a user-defined type. Type parameters can be used to specify the parameter types of the function template itself, the return value type, and declare local variables in the function.

For example, the function above could be replaced with a function template:

template<typename T>
T abs(T x){
    return x<0?-x:x;
}

How to correctly understand function templates

A function template is not a real function. It is just a compilation instruction. When a function of a certain type is called in the context of your program, it will actually be expanded into a function of this type.

It may sound a bit abstract, but we can prove it clearly by observing the assembly code. (Here I recommend a more useful website to view assembly code in real time: Compiler Explorer (godbolt.org)

First, we define a normal function:

int abs(int x){
    return x<0?-x:x;
}

Using the website just now, take a look at its assembly code:

Please add a picture description

Then, we define a function template:

template<typename T>
T abs(T x){
    return x<0?-x:x;
}

Using the website just now again, take a look at its assembly code:

Please add a picture description

You will find that the corresponding assembly code is empty! And when we call a certain type of function below:

Please add a picture description

did you see it? Here it generates two types of abs functions, this is because we call these two types of functions in the main function, that is, only when we actually call, this type of function will pass through the function template This "blueprint" is generated. Just write a template function without calling it, and no function will be generated!

Use of function templates

Above we call the abs function like this:

abs<int>(1);
abs<double>(1.0);

This is through explicit calls, and there is a more convenient implicit call:

abs(1);
abs(1.0);

In this case, the compiler will automatically deduce what type of abs function needs to be generated according to the parameter type.

A template is like an employee you hire in C++, you order it, and it writes the code for you. Of course, the premise is that your order is correct.

The abs function is relatively simple, let's look at another function:

template<typename T>
T add(T x,T y){
    return x+y;
}

If we use it like this:

cout<<add(1,1)<<endl;
cout<<add(1,2.9)<<endl;//这里由于两个参数类型不一致会报错

How to fix this?

First, we can explicitly call the add function:

cout<<add(1,1)<<endl;
cout<<add<int>(1,2.9)<<endl;

At this time, the formal parameter will be converted to an int type, so that the add function can be called normally.

There is a better way:

template<typename T,typename U>
T add(T x,U y){
    return x+y;
}

We can define two kinds of parameters, but this time the problem comes again: we can distinguish the type of x and y, but what about the type of x+y? That is to say, how to determine the return value type? Here is a keyword: decltype.

what is decltype

​ decltype is a new keyword added by C++11. It has the same function as auto and is used for automatic type deduction at compile time. The reason why decltype is introduced is that auto is not suitable for all automatic type deduction scenarios. In some special cases, auto is inconvenient to use, or even impossible to use.

auto varName=value;
decltype(exp) varName=value;
  • auto deduces the variable type according to the initial value on the right side of =, and decltype deduces the variable type according to the exp expression, which has nothing to do with the value on the right side of =;

  • Auto requires that variables must be initialized, because auto deduces the variable type based on the initial value of the variable. If it is not initialized, the variable type cannot be deduced, but decltype does not require it, so it can be written as follows

    decltype(exp) varName;
    

    In principle, exp is just an ordinary expression, which can be in any complex form, but it must be guaranteed that the result of exp is typed and cannot be void; if exp is a function whose return value is void, the result of exp It is also a void type, which will cause a compilation error at this time

    int x = 0;
    decltype(x) y = 1;           // y -> int
    decltype(x + y) z = 0;       // z -> int
    const int& i = x;
    decltype(i) j = y;           // j -> const int &
    const decltype(z) * p = &z;  // *p  -> const int, p  -> const int *
    decltype(z) * pi = &z;       // *pi -> int      , pi -> int *
    decltype(pi)* pp = &pi;      // *pp -> int *    , pp -> int * *
    

decltype derivation rules

  • If exp is an expression not surrounded by parentheses (), or a class member access expression, or a single variable, the type of decltype(exp) is consistent with exp;
  • If exp is a function call, the type of decltype(exp) is consistent with the type of the function return value;
  • If exp is an lvalue, or surrounded by parentheses (), the type of decltype(exp) is a reference to exp, assuming that the type of exp is T, then the type of decltype(exp) is T&;
  • You can use auto for static members of a class, but you cannot use auto for non-static members of a class. If you want to deduce the type of a non-static member of a class, you can only use decltype.

Lvalue: the data that still exists after the execution of the expression, that is, persistent data; rvalue refers to the data that no longer exists after the execution of the expression, that is, temporary data.

A simple way to distinguish is: take the address of the expression, if the compiler does not report an error, it is an lvalue, otherwise it is an rvalue

template<typename T>
class A
{
private :
   decltype(T.begin()) m_it;
   //typename T::iterator m_it;   //这种用法会出错
public:
void func(T& container)
{
   m_it=container.begin();
}
};

int main()
{
const vector<int> v;
A<const vector<int>> obj;
obj.func(v);
return 0;
}

Now with this keyword, you might say that the function template can be written like this:

template<typename T,typename U>
decltype(x+y) add(T x,U y){
    return x+y;
}

But have you ever thought that when the compiler executes decltype, it still doesn't know what type (x+y) is, so naturally it cannot be deduced.

So how should it be written? Here is a solution: use the constructor:

template<typename T,typename U>
decltype(T()+U()) add(T x,U y){
    return x+y;
}

Using this at that time, the addition of two numbers of different types can be realized.

At this time you may say, what if T and U do not have default constructors? Like this situation:

#include <iostream>

using namespace std;

class A{
	int x;
	public:
		A() = delete;
		A(int x):x(x){}
		
		A operator+(const A& b){
			return (x+b.x);
		}
		
		friend ostream& operator<<(ostream & os , const A& a){
			return os<<a.x;
		}
};

template<typename T,typename U>
decltype(T()+U()) add(T x,U y){
    return x+y;
}

int main(){
    A aa(3),bb(4);
    cout<<add(aa,bb)<<endl;
    return 0;
}

What should we do in this situation?

Regarding the return value of the function, in fact, C++ provides two ways, and we usually use only one of them:

int getSum(int a, int b);

There is another way called return type postfix

auto getSum(int a, int b)->int;

At that time, the latter usage of post-return type will be used:

#include <iostream>

using namespace std;

class A{
	int x;
	public:
		A() = delete;
		A(int x):x(x){}
		
		A operator+(const A& b){
			return (x+b.x);
		}
		
		friend ostream& operator<<(ostream & os , const A& a){
			return os<<a.x;
		}
};

template<typename T,typename U>
auto add(T x,U y)->decltype(x+y)
{
    return x+y;
}

int main(){
    A aa(3),bb(4);
    cout<<add(aa,bb)<<endl;
    return 0;
}

Function Template Reification

We discussed the meaning of function templates before. Obviously, we can implement generic programming by using function templates reasonably, that is to say, define a function template, and then ideally support any form of invocation. But in fact, even if the function templates we define can meet our needs in most cases, there are always some special cases that we need to deal with specially, so there are implicit instantiation, explicit instantiation, and partial specificity. explicit reification. These are collectively called function reifications.

implicit instantiation

The two instantiation methods we mentioned above belong to implicit instantiation. The characteristic of this instantiation is that only when we really need a function, the function template will instantiate this type of function for us.

explicit instantiation

Compared with the previous implicit instantiation, if the user confirms that a certain type of function will be used in the future, we can explicitly instantiate it. The relevant syntax is:

template void Swap<int>(int,int);//显式实例化

After the compiler sees the above declaration, it will use the Swap( ) template to generate an instance using the int type. In other words, the declaration means "use the Swap( ) template to generate a function definition of type int.". The advantage of this instantiation is that it saves the time of generating this function in the compiler.

explicit reification

For the function template just now:

template<typename T>
T abs(T x){
    return x<0?-x:x;
}

Sometimes it may be required to perform special processing for a certain type, such as not only returning, but also printing output. At this time, it is necessary to "specialize" for this situation:

template <> 
T abs<int>(int x){//这里可以省略<int>
    cout<<a<<endl;
    return x<0?-x:x;
} 

calling rule

  • If both the function template and the normal function can be implemented, the normal function is called first; the calling order: common function>display specificity>template function
void myPrint(int a, int b)
{
	cout << "调用的普通函数" << endl;
}
 
template<typename T>
void myPrint(T a, T b)
{
	cout << "调用的是函数模板" << endl;
}
void Mytest()
{
	int a = 10;
	int b = 20;
	myPrint(a, b);
 
}	   
int main()
{
	Mytest();
	return 0;
}

//输出:调用的普通函数
  • An empty template argument list can be used to force a function template to be invoked;
void myPrint(int a, int b)
{
	cout << "调用的普通函数" << endl;
}
 
template<typename T>
void myPrint(T a, T b)
{
	cout << "调用的是函数模板" << endl;
}
void Mytest()
{
	int a = 10;
	int b = 20;
	myPrint<>(a, b);    //通过空模板参数列表来强制调用函数模板
}   
int main()
{
	Mytest();
	return 0;
}

//输出:调用的是函数模板

Function templates can also be overloaded;

template<typename T>
void myPrint(T a, T b)
{
	cout << "调用的是函数模板" << endl;
}
 
template<typename T>
void myPrint(T a, T b,T c)
{
	cout << "调用的是重载的函数模板" << endl;
}
void Mytest()
{
	int a = 10;
	int b = 20;
	myPrint(a, b, 100);
 
}  
int main()
{
	Mytest();
	return 0;
}

//输出:调用的是重载的函数模板

If the function template can produce a better match, the function template is called first;

void myPrint(int a, int b)
{
	cout << "调用的普通函数" << endl;
}
 
template<typename T>
void myPrint(T a, T b)
{
	cout << "调用的是函数模板" << endl;
}
void Mytest()
{
	char c1 = 'a';
	char c2 = 'b';
	myPrint(c1, c2);
}   
int main()
{
	Mytest();
	return 0;
}

//调用的是函数模板

Overloading and nesting of templates;

#include <iostream>
 
using namespace std;
template <class T>
T Max(T a,T b)
{
    return((a > b)?a : b);
}
template <class T>
T Max(T a,T b,T c)
{
    return Max(Max(a,b),c);    //嵌套
}
template <class T>
T Max(T a,T b,T c,T d)
{
    return Max(Max(a,b,c),d);    //嵌套
}
 
int main(int argc, char *argv[])
{
    cout << Max(8,11) << endl;
    cout << Max(4,8,9) << endl;
    cout << Max(4.2,2.8,5.9) << endl;
 
    cout << Max<float>(5.4,3.8,2.9) << endl;
    cout << Max<float>(5.4,3.8,2.9,8.1) << endl;
    return 0;
}

//输出结果
11
9
5.9
5.4
8.1

--------------------------------
Process exited after 0.08955 seconds with return value 0
请按任意键继续. .

Summarize

The template is a general framework and the main embodiment of the idea of ​​C++ generic programming. The main differences between ordinary functions and function templates are as follows:

  • Ordinary functions can only have one data type to match. There are many types of function templates
  • Implicit deduction uses ordinary functions first, and only uses function templates when ordinary functions do not match
  • Function templates only construct functions when they are called, while ordinary functions are compiled at compile time
  • Automatic type conversion can occur when ordinary functions are called, but function templates cannot
int myAdd1(int a, int b)    //普通函数
{
	return a + b;
}
 
template<class T>
int myAdd2(T a, T b)    //函数模板
{
	return a + b;
}
 
void Mytest()
{
	int a = 10;
	int b = 20;
	char c = 'c';
	cout << myAdd1(a, c) << endl;
 
	//隐式类型推导
	cout << myAdd2(a, b) << endl;
	//cout << myAdd2(a, c) << endl;会报错,T的类型不一致
 
	//显示指定类型
	myAdd2<int>(a, c); //不会报错,会发生隐式类型转换
}
	   
int main()
{
	Mytest();
	return 0;
}

Guess you like

Origin blog.csdn.net/weixin_43717839/article/details/131519142