Templates for C++ Generic Programming

Table of contents

1. What is generic programming 

2. Function template

2.1 The concept of function template

2.2 Function template format

2.3 The principle of function template

 2.5 Instantiation of function templates

2.6 Matching principle of template parameters

3. Class template

3.1 Definition format of class template

3.2 Instantiation of class templates

4. Non-type template parameters

5. Template specialization

5.1 The concept of template specialization:

5.2 Function template specialization

5.3 Specialization of class templates

5.4 Application Scenarios of Template Specialization

6. Separate compilation of templates 

6.1 What is separate compilation of templates

6.2 Separate compilation of templates

6.3 Solutions

Seven, template summary


1. What is generic programming 

In C language, when we need to use a function to exchange the values ​​of two variables of type int, we can definitely think of defining a function of type int directly, but at this time we want to exchange variables of type double. Thinking of defining a function that exchanges double types, someone in C++ will think of function overloading, such as:

#include<iostream>
using namespace std;
void Swapi(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}
void Swapd(double& left, double& right)
{
	double temp = left;
	left = right;
	right = temp;
}
void Swapc(char& left, char& right)
{
	char temp = left;
	left = right;
	right = temp;
}
int main()
{
	int a = 1, b = 2;
	double c = 3.000, d = 4.000;
	char e = 'A', f = 'B';
	Swapi(a, b);
	Swapd(c, d);
	Swapc(e, f);
	cout << "a= " << a << ',' << "b= " << b << endl;
	cout << "c= " << c << ',' << "d= " << d << endl;
	cout << "e= " << e << ',' << "f= " << f << endl;
	return 0;
}

Although this can be achieved, the reusability of the code is too low. Every time a type is implemented, a function must be manually implemented.

Although it is possible to use function overloading, it has the following disadvantages:

  •  The overloaded functions are only of different types, and the code reuse rate is relatively low. As long as a new type appears, the user needs to add the corresponding function
  • The maintainability of the code is relatively low, one error may cause all overloads to be error

Is this very troublesome? Not only does the amount of code increase, but the reusability of the code is also relatively low. Therefore, at this point we can use function templates to complete the writing of functions, such as:

#include<iostream>
using namespace std;
//函数模板
template <class T>
void Swap(T& a, T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
}
int main()
{
	int a = 1, b = 2;
	double c = 3.000, d = 4.000;
	char e = 'A', f = 'B';
	Swap(a, b);
	Swap(c, d);
	Swap(e, f);
	cout << "a= " << a << ',' << "b= " << b << endl;
	cout << "c= " << c << ',' << "d= " << d << endl;
	cout << "e= " << e << ',' << "f= " << f << endl;
	return 0;
}

The template is like movable type printing, we can realize the font we want by giving us a template

If such a mold can also exist in C++, by filling the mold with different materials (types) to obtain text of different materials (that is, generate specific types of codes), it will save a lot of hair. Coincidentally, the predecessors have already planted the tree, and we only need to enjoy the shade here.
Generic programming : Writing generic code that has nothing to do with types is a means of code reuse. Templates are the foundation of generic programming.

2. Function template

2.1 The concept of function template

A function template represents a family of functions. The function template has nothing to do with the type. It is parameterized when used, and a specific type version of the function is generated according to the type of the actual parameter.

2.2 Function template format

template<typename T1, typename T2,...,typename Tn>
return value type function name (parameter list) { function body}

template<typename T>//其中typename也可以改为class
void Swap( T& a, T& b)
{
 T temp = a;
 a = b;
 b = temp;
}

Note : typename is used to define template parameter keywords , and class can also be used (remember: struct cannot be used instead of class)

2.3 The principle of function template

A function template is a blueprint, which is not a function itself, but a mold for the compiler to generate a specific type of function by using it. So in fact, the template is to hand over the repetitive things that we should have done to the compiler.

 In the compiler compilation stage , for the use of template functions, the compiler needs to deduce and generate corresponding types of functions for calling according to the type of actual parameters passed in. For example: when using a function template with a double type, the compiler determines T to be a double type through the deduction of the actual parameter type, and then generates a code that specifically handles the double type, and the same is true for the character type.

 2.5 Instantiation of function templates

When a function template is used with parameters of different types, it is called instantiation of the function template. Template parameter instantiation is divided into : implicit instantiation and explicit instantiation
Implicit instantiation : Let the compiler deduce the actual type of the template parameter according to the actual parameter

#include<iostream>
using namespace std;
//函数模板
template <class T>
void Swap(T& a, T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
}
int main()
{
	int a = 1, b = 2;
	char c = 'A', d = 'B';
	Swap(a, b);
	Swap(c, d);
}

Conflicts that can occur when implicitly instantiated

#include<iostream>
using namespace std;
//函数模板
template <class T>
void Swap(T& a, T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
}
int main()
{
	int a = 1;
	double b = 3.000;
	Swap(a, b);//编译阶段报错
}

The Swap(a, b); statement in the above program will report an error, because during compilation, when the compiler sees the instantiation, it needs to deduce its parameter type. T is deduced into int through the actual parameter a, and the T is deduced as a double type, but there is only one T in the template parameter list, the compiler cannot determine whether T should be determined to be an int or a double type, and an error is reported Note: In templates, the compiler generally does not perform type conversion operations,
because Once there is a problem with the conversion, the compiler needs to take the blame

At this point there are two solutions,

(1) Mandatory type conversion first before parameter passing

Swap(a, (int) b);
//或
//Swap( (double) a,  b);

(2) Display instantiation

Display instantiation : specify the actual type of the template parameter in <> after the function name

#include<iostream>
using namespace std;
//函数模板
template <class T>
void Swap(T a, T b)
{
	T tmp = a;
	a = b;
	b = tmp;
}
int main()
{
	int a = 1;
	double b = 3.000;
//显示实例化
	Swap<int>(a, b);//表示T此时为int类型
   //Swap<double>(a, b); 表示T此时为double类型
}

If the types do not match, the compiler will try to perform an implicit type conversion, and if the conversion fails, the compiler will report an error.

2.6 Matching principle of template parameters

① A non-template function can exist at the same time as a function template with the same name, and the function template can also be instantiated as this non-template function

#include<iostream>
using namespace std;
//专门交换int类型的函数
void Swap(int a,int b)
{
   int tmp = a;
     a = b;
     b = tmp;
}
//通用交换模板函数
template <class T>
void Swap(T a, T b)
{
	T tmp = a;
	a = b;
	b = tmp;
}
int main()
{
	int a = 1, b = 2;
	char c = 'A', d = 'B';
	Swap(a, b);//与非模板函数匹配,编译器不使用模板实例化int类型函数
	Swap(c, d);//与模板函数匹配,编译器使用模板实例化double类型函数
   //注意下面这行会发生隐式类型转换
    Swap<int>(c,d);//与模板函数匹配,编译器使用模板实例化int类型函数
}

Note: Implicit type conversion will occur in the last line of exchange, resulting in a temporary variable, which cannot be passed by reference at this time, because the temporary variable is constant

② For a non-template function and a function template with the same name, if other conditions are the same, the non-template function will be called first and an instance will not be generated from the template when mobilizing . If the template can produce a function with a better match, then the template will be chosen

#include<iostream>
using namespace std;
//专门交换int类型的函数
void Swap(int a,int b)
{
   int tmp = a;
     a = b;
     b = tmp;
}
//通用交换模板函数
template <class T>
void Swap(T1 a, T2 b)
{
	T tmp = a;
	a = b;
	b = tmp;
}
int main()
{
	int a = 1, b = 2;
	double d=3.000;
	Swap(a, b);// 与非函数模板类型完全匹配,不需要函数模板实例化
	Swap(a, d);// 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Swap函数
}

③Template functions do not allow automatic type conversion, but ordinary functions can perform automatic type conversion

3. Class template

3.1 Definition format of class template

template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
#include<iostream>
using namespace std;
//函数模板
template <class T>
class A
{
public:
	A(T a,T b)
		:_a(a)
		,_b(b)
	{}
	void print()
	{
		cout << _a << ',' << _b << endl;
	}
private:
	T _a;
	T _b;
};
int main()
{
	A<int> a(2,4);//类模板必须显示实例化
	a.print();
}

Note: When a function in a class template is defined outside the class, a template parameter list needs to be added,
such as:

#include<iostream>
using namespace std;
//函数模板
template <class T>
class A
{
public:
	A(T a,T b)
		:_a(a)
		,_b(b)
	{}
	void print();
private:
	T _a;
	T _b;
};
注意:类模板中函数放在类外进行定义时,需要加模板参数列表
template <class T>
void print()
{
	cout << _a << ',' << _b << endl;
}
int main()
{
	A<int> a(2,4);
	a.print();
}

Because the template parameters also have a scope, just like the above class A, after class A, you need to write the template parameters again when you need to define the print function, because the scope of the parameters of the class template only scopes the class

3.2 Instantiation of class templates

Class template instantiation is different from function template instantiation. Class template instantiation needs to follow the class template name with <>, and then put the instantiated type in <>. The class template name is not a real class, but the instantiated The result is the real class
 

//A表示类名,A<int>才表示类型
A<int> a1;
A<double> a2;

Note: the instantiation of the class must show instantiation

4. Non-type template parameters

Template parameters are divided into type parameters and non-type parameters .
Type parameter: Appears in the template parameter list, followed by the parameter type name such as class or typename.
Non-type parameter: It is to use a constant as a parameter of a class (function) template, and the parameter can be used as a constant in the class (function) template.

#include<iostream>
using namespace std;
namespace lx
{
	// 定义一个模板类型的静态数组
	template<class T, size_t N = 10>
	class array
	{
	public:
		T& operator[](size_t index) 
		{ 
			return _array[index]; 
		}
		const T& operator[](size_t index)const 
		{ 
			return _array[index]; 
		}
		size_t size()const 
		{ 
			return _size; 
		}
		bool empty()const
		{ 
			return 0 == _size;
		}
	private:
		T _array[N];
		size_t _size;
	};
}

Note:
1. Floating point numbers, class objects, and strings are not allowed as non-type template parameters.
2. Non-type template parameters must be able to confirm the result at compile time.

5. Template specialization

5.1 The concept of template specialization:

Usually, templates can be used to implement some type-independent codes, but for some special types, some wrong results may be obtained and special handling is required, for example: a function template specially used for less than comparison is implemented

#include<iostream>
using namespace std;
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
	return left < right;
}
int main()
{
	cout << Less(1, 2) << endl; // 可以比较,结果正确
	double d1 = 4.0, d2 = 5.0;
	cout << Less(d1, d2) << endl; // 可以比较,结果正确
	double* p1 = &d1;
	double* p2 = &d2;
	cout << Less(p1, p2) << endl; // 可以比较,结果错误
	return 0;
}


 

 It can be found that the final result does not match our expected result. Our original intention is to compare the values ​​of d1 and d2, but it turns out that it compares the addresses of d1 and d2, which is contrary to our original intention. So how can we achieve the desired effect?

At this point, the template needs to be specialized . That is: on the basis of the original template class, it is a specialized implementation method for a special type. Template specialization is divided into function template specialization and class template specialization.

5.2 Function template specialization

The specialization steps of a function template:

  1.  There must be a basic function template first
  2.  The keyword template is followed by a pair of empty angle brackets <>
  3.  The function name is followed by a pair of angle brackets, which specify the type to be specialized
  4.  Function parameter table: It must be exactly the same as the basic parameter type of the template function. If it is different, the compiler may report some strange errors.
     
#include<iostream>
using namespace std;
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
	return left < right;
}
// 对Less函数模板进行特化
template<>
bool Less<double*>(double* left, double* right)
{
	return *left < *right;
}
int main()
{
	cout << Less(1, 2) << endl;
	double d1 = 4.0;
	double d2 = 5.0;
	cout << Less(d1, d2) << endl;
	double* p1 = &d1;
	double* p2 = &d2;
	cout << Less(p1, p2) << endl; // 调用特化之后的版本,而不走模板生成了
	return 0;
}

 It can be found that the expected results are in line with the

Note: In general, if the function template encounters a type that cannot be processed or is processed incorrectly, the function is usually given directly for simplicity of implementation.

bool Less(double* left, double* right)
{
   return *left < *right;
}

This kind of implementation is simple and clear, the code is highly readable, and easy to write, because for some function templates with complex parameter types, specialization is given during specialization, so specialization is not recommended for function templates.

5.3 Specialization of class templates

Class template specialization is divided into full specialization and partial specialization

(1) Full specialization: determinize all parameters in the template parameter list

#include<iostream>
using namespace std;
template<class T1, class T2>
class Date
{
public:
	Date() { cout << "Date<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};
//类模板全特化
template<>
class Date<int, char>
{
public:
	Date() { cout << "Date<int, char>" << endl; }
private:
	int _d1;
	char _d2;
};
void TestVector()
{
	Date<int, int> d1;
	Date<int, char> d2;
}

Note: The writing position of the class template specialization template parameter

(2) Partial specialization: any specialized version that further restricts the design of template parameters

① Partial specialization
Specialize part of the parameters in the template parameter class table

// 将第二个参数特化为int
template <class T1>
class Date<T1, int>
{
public:
Date() {cout<<"Date<T1, int>" <<endl;}
private:
T1 _d1;
int _d2;
};

②Further restrictions on parameters

Partial specialization does not just refer to specialization of some parameters, but a specialized version designed for further conditional restrictions on template parameters, such as:

Specialized as a pointer type

//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
	Data() { cout << "Data<T1*, T2*>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

specialize as a reference type 

//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
	Data(const T1& d1, const T2& d2)
		: _d1(d1)
		, _d2(d2)
	{
		cout << "Data<T1&, T2&>" << endl;
	}

calling method

Data<double , int> d1; // 调用特化的int版本
Data<int , double> d2; // 调用基础的模板
Data<int *, int*> d3; // 调用特化的指针版本
Data<int&, int&> d4(1, 2); // 调用特化的指针版本

5.4 Application Scenarios of Template Specialization

When we need to define a functor, it is usually defined using a class template, such as:

template<class T>
struct Less
{
bool operator()(const T& x, const T& y) const
{
   return x < y;
}
};

The sort sort in the C++ standard library has a template parameter of this type for sorting
 

#include<iostream>
using namespace std;
#include<vector>
#include <algorithm>
template<class T>
struct Less
{
	bool operator()(const T& x, const T& y) const
	{
		return x < y;
	}
};
int main()
{
	double d1 = 3.0;
	double d2 = 4.0;
	double d3 = 2.0;

	vector<double> v1;
	v1.push_back(d1);
	v1.push_back(d2);
	v1.push_back(d3);
	// 可以直接排序,结果是升序
	sort(v1.begin(), v1.end(), Less<double>());
	auto it = v1.begin();
	while (it != v1.end())
	{
		printf("%lf ", *it);
		it++;
	}
  return 0;
}

It can be found that the sorting is completed, but what happens when the address we pass in?

#include<iostream>
using namespace std;
#include<vector>
#include <algorithm>
template<class T>
struct Less
{
	bool operator()(const T& x, const T& y) const
	{
		return x < y;
	}
};
int main()
{
	double d1 = 3.0;
	double d2 = 4.0;
	double d3 = 2.0;

	vector<double*> v2;
	v2.push_back(&d1);
	v2.push_back(&d2);
	v2.push_back(&d3);
	// 可以直接排序,结果错误,不是升序,而v2中放的地址是升序
	// 此处需要在排序过程中,让sort比较v2中存放地址指向的日期对象
	// 但是走Less模板,sort在排序时实际比较的是v2中指针的地址,因此无法达到预期
	sort(v2.begin(), v2.end(), Less<double*>());
	auto it1 = v2.begin();
	while (it1 != v2.end())
	{
		cout << *(*it1) << ' ';
		it1++;
	}

	return 0;
}

 

 It can be found that its results are not in ascending order, which does not match the results we want. Why is this happening?

Reason: When it calls the sort function to sort, it compares the address we pass in, not the value stored in the address passed in, so it sorts the addresses.

This is obviously not what we want, so we can use template specialization to deal with it at this time

#include<iostream>
using namespace std;
#include<vector>
#include <algorithm>
template<class T>
struct Less
{
	bool operator()(const T& x, const T& y) const
	{
		return x < y;
	}
};
// 对Less类模板按照指针方式特化
template<>
struct Less<double*>
{
	bool operator()(double* x, double* y) const
	{
		return *x < *y;
	}
};
int main()
{
	double d1 = 3.0;
	double d2 = 4.0;
	double d3 = 2.0;

	vector<double*> v2;
	v2.push_back(&d1);
	v2.push_back(&d2);
	v2.push_back(&d3);
	// 可以直接排序,结果错误,不是升序,而v2中放的地址是升序
	// 此处需要在排序过程中,让sort比较v2中存放地址指向的日期对象
	// 但是走Less模板,sort在排序时实际比较的是v2中指针的地址,因此无法达到预期
	sort(v2.begin(), v2.end(), Less<double*>());
	auto it1 = v2.begin();
	while (it1 != v2.end())
	{
		cout << *(*it1) << ' ';
		it1++;
	}

	return 0;
}

 

This has the expected result, so the specialization of the template is still crucial.

6. Separate compilation of templates 

6.1 What is separate compilation of templates

A program (project) is jointly implemented by several source files, and each source file is compiled separately to generate an object file, and finally the process of linking all object files to form a single executable file is called separate compilation mode.

6.2 Separate compilation of templates

 The declaration and definition of the template are separated, the declaration is made in the header file, and the definition is completed in the source file:

// a.h
template<class T>
T Add(const T& left, const T& right);
// a.cpp
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
// main.cpp
#include"a.h"
int main()
{
Add(1, 2);
Add(1.0, 2.0);
return 0;
}

This will cause an error for the following reasons: 


 

If the template declaration and definition are in different files, the address of the function will not be found and an error will occur during the linking stage, so it is recommended that the template declaration and definition be in the same file

6.3 Solutions

① It is actually possible to put the declaration and definition in a file "xxx.hpp" or xxx.h. This is recommended.

// a.h
template<class T>
T Add(const T& left, const T& right);
// a.h
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
// main.cpp
#include"a.h"
int main()
{
Add(1, 2);
Add(1.0, 2.0);
return 0;
}

② The location of the template definition is explicitly instantiated. This method is impractical and not recommended.

// a.h
template<class T>
T Add(const T& left, const T& right);
// a.cpp
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
//函数显示实例化,为int类型
template
int Add<int>(const int& left,const int& right);
//类显示实例化,为int类型
//template
//vector<int>;
// main.cpp
#include"a.h"
int main()
{
Add(1, 2);
Add(1.0, 2.0);
return 0;
}

This method is to instantiate directly in the function or class definition file, and the address can be found when calling, because they have been instantiated when linking, and the address enters the symbol table, and in the symbol table Their addresses can be found so that no errors are reported. But the disadvantage of this method is obvious. Every time you change a type, you have to display and instantiate it once , which seems a bit dull and troublesome, so this method is not recommended.

Seven, template summary

1. Advantages

  • The template reuses the code, saves resources, and enables faster iterative development. The C++ Standard Template Library (STL) is thus produced
  •  Enhanced code flexibility

2. Disadvantages

  • Templates can lead to code bloat and longer compile times
  • When a template compilation error occurs, the error message is very messy and it is not easy to locate the error


That’s all for sharing the knowledge of templates. Please point out any omissions or mistakes. If it helps you, please pay attention to Bo. Thank you for your support, 886!

Guess you like

Origin blog.csdn.net/m0_72532428/article/details/130815436