[C++ Advanced Road] Template

Preface

Suppose you need to write a swap function to exchange two values ​​of the same type. At this time, if the value exchanged is an int type, you may write a Swap function in which the parameters are two int types. If you are asked to write another What about double type? You may have to write another Swap function overload. The parameters are two types of double. What if you are asked to write another char type? You might... is there an end to this? No, because there are also custom types! So how do we solve this problem? ——Template (the main function is to achieve universal use)

  • Supplement: Generic programming: Writing universal code that is independent of type is a means of code reuse. Templates are the basis of generic programming.

1. Function template

Basic usage

①Define template parameter type name

template<typename T1, typename T2,......,typename Tn>
//这里的typename也可以换为class
template<class T1, class T2,......,class Tn>
  • Remember: cannot be replaced with struct

②Implementation of function template

  • For example, if we implement a template for the exchange function
template<typename T>
void Swap(T& n1, T& n2)
{
    
    
	T tmp = n1;
	n1 = n2;
	n2 = tmp;
}

Experiment with the following examples:
Example 1:

class Date
{
    
    
public:

	void Print()
	{
    
    
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
	Date(int year,int month,int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{
    
    

	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	int x = 0,y = 1;
	double x1 = 1.1, y1 = 2.2;
	Date x2(1949, 10, 1), y2(2023, 5, 23);
	Swap(x, y);
	Swap(x1, y1);
	Swap(x2, y2);
	x2.Print();
	y2.Print();
	cout << "x:" << x << " y:" << y << endl;
	cout << "x1:" << x1 << " y1:" << y1 << endl;
	return 0;
}

operation result:
Insert image description here

  • You can see that this is an exchange.

At this point we have another question - is a function being called?
Continue the analysis - go to the disassembly .
Insert image description here

  • You can see that the addresses of the Swap being called are different!
  • Therefore: what is being called is not a function.
  • How to explain this?

Changing the soup does not change the medicine, but changing the type and the idea of ​​exchange remains unchanged.
Illustration:
Insert image description here

In fact, we don’t need to implement the exchange function ourselves at all, we can just use the one in the library.Insert image description here

  • However, the one in the library is all lowercase, and what we usually implement is the first letter size.

③Instantiation of template


implicit instantiation

  • Let the compiler deduce the actual type of the template parameter from the actual parameter .
template <typename T>
T Func(const T& n1, const T& n2)
{
    
    
	return n1 + n2;
}
int main()
{
    
    
	int x = 0,y = 1;
	Func(x, y);
	return 0;
}
  • Just like the parameter passed here does not explicitly state the type of x, but the compiler will automatically deduce the type of the actual parameter.

show instantiation

  • Specify the actual type of the template parameter in <> after the function name
template <typename T>
T Func(const T& n1, const T& n2)
{
    
    
	return n1 + n2;
}

int main()
{
    
    
	int x = 0,double y = 1;
	Func<int>(x, y);
	//这里会将y强制类型转换为一个double类型的常量,因此函数参数的const不可省去。
	return 0;
}
  • Like this, if the compiler cannot deduce the type, then we need to explicitly declare what the type is, otherwise the compiler will directly report an error.
  • Is there any other way besides displaying examples? ——Forced type conversion.
template <typename T>
T Func(const T& n1, const T& n2)
{
    
    
	return n1 + n2;
}

int main()
{
    
    
	int x = 0,double y = 1;
	Func(x, (int)y);
	//这里会将y强制类型转换为一个double类型的常量,因此函数参数的const不可省去。
	return 0;
}
  • The result of the forced conversion must be to make the two parameter types consistent. If the parameter types after the forced conversion are inconsistent, the compiler will still report an error.

There is another situation where we must show the instantiation - the template parameter is used as the return value .
Example:

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

int main()
{
    
    
	int x = 0;
	int y = 1;
	Func<int>(x,y);
	return 0;
}
  • This is just like if you want to use a function template, you must first let the compiler know or be able to deduce the types of template parameters. If the return value is written with template parameters, the compiler has no way to start. Then the only way is explicit instantiation. The compiler Only when the return type is known can a function of the specified type be generated for you.

④Calling rules for function templates and ordinary functions

1. Use ordinary functions if you can, and use function templates if you can’t.

int add(int x, int y)
{
    
    
	return x + y;
}

template <typename T1,typename T2>
int add(T1 x, T2 y)
{
    
    
	return x + y;
}

int main()
{
    
    
	add(1, 2);//这里调用的非模板
	add(1.0, 1);//这里使用的是模板生成的函数
	return 0;
}

Look at the disassembly:
Insert image description here

2. Both non-template functions and template parts can be used repeatedly.

int add(int x, int y)
{
    
    
	return x + y;
}
template <typename T1,typename T2>
int add(T1 x, T2 y)
{
    
    
	return x + y;
}
int main()
{
    
    
	add(1, 2);//这里是调用add函数,因为能用普通函数就用普通函数
	add<int>(1, 1);这里是用的add模板函数实例化生成的函数。
	//这样也可以
	add<>(1, 1);//这算是空模板,只是为了调用模板而已。
	return 0;
}

3. Ordinary functions support type conversion, while templates must strictly follow types.

template<typename T>
int myAdd(T a, T b)
{
    
    
	cout << "template function" << endl;
	return a + b;
}

int myAdd(char a, char b)
{
    
    
	cout << "normal function" << endl;
	return a + b;
}
void test()
{
    
    
	int a = 10;
	int b = 20;
	char c1 = 'a';
	char c2 = 'b';

	myAdd(a, c1);//这里调用的是函数,这里的a会自动转换为char类型的
	myAdd(a, b);//两个都是int类型的调用模板函数
	myAdd(c1, b);//这里会发生类型转换,b的int类型会转换为char类型的
	myAdd(c1, c2);
	myAdd<>(c1, c2);//这里是显示实例化,自然会调用函数模板
}
int main()
{
    
    
	test();
	return 0;
}

  • Summarize:

1. Ordinary functions are called first
2. Ordinary functions support type conversion
3. Templates must strictly match types
4. If the function template can produce a better match, then choose the template.
5. You can use an empty template or template instantiation to let the compiler only call the template.

2. Class template

  • In short, classes are universal. For example, a stack must store both int type and double type, etc.
  • Note: A class template is not a specific class, but a mold used to generate class objects.

  • Supplement: The template parameters defined by template are only valid within the nearest brace.
#include<iostream>
using namespace::std;
template <typename T>
class Stack
{
    
    
public:
	Stack(int capacity = 4)
	{
    
    
		cout << "Stack()" << endl;
		T* tmp = new T[capacity];
		_arr = tmp;
		_top = 0;
		_capacity = capacity;
	}
	void PushBack(T x);

private:
	T* _arr;
	int _top;
	int _capacity;
};
//这里放在类外进行定义。
template <typename T>
//这里要说明的是必须写Stack<T>这是具体的类,而不能只写Stack。
void Stack<T>::PushBack(T x)
{
    
    
	if (_top == _capacity)
	{
    
    
		T* tmp = (T*)realloc(_arr, sizeof(T) * _capacity * 2);
		if (tmp == NULL)
		{
    
    
			perror("realloc fail");
			exit(-1);
		}
		else
		{
    
    
			_arr = tmp;
			_capacity *= 2;
		}
	}
	_arr[_top++] = x;
}
int main()
{
    
    
	Stack<int> stack1;//这要说明使用的数据类型,也就是类模板示例化。
	return 0;
}

// 注意:类模板
//1.类名——Stack
//2.类型 ——Stack<T>
  • A class instantiated from a class template is called a template class

3. Advanced knowledge

①Special usage of typename

In most cases, class and typename have the same purpose. The following is the difference.

//typename的作用——避免二义性

//const_iterator是静态变量
//struct A
//{
    
    
//	static int const_iterator;
//};
//int A::const_iterator = 1;

template<typename Container>
void Print(const Container& ret)
{
    
    
	typename Container::const_iterator it = ret.begin();
	//主要为Container::const_iterator可能是类型,也可能是一个静态变量。
	//可以替换为
	//auto it = ret.begin();
	for (auto e : ret)
	{
    
    
		cout << e << " ";
	}
	cout << endl;
}
  • The const_iterator here may be a static variable or a type, which is ambiguous.
  • Therefore, adding typename here can declare the type.

②Static stack


Code 1:


#define N 100
	template<class T>
	class stack
	{
    
    
	public:
		T arr[N];//这样N是固定的
	};

  • N cannot be modified while in use, except by modifying the source code.

Code 2:

	template<class T, size_t N = 100>
					//这里只能是 char、short、int、size_t类型的
	class stack
	{
    
    
	public:
		T arr[N];//这样N可以通过传参进行控制
	};
  • N can be controlled by passing parameters, but the type here must be in the integer family .

This is how Curry’s arry is implemented:

array<int,10> a;
//说明初始化10个元素,每个元素为int类型
int arr[10];

//唯一的区别:array进行强制了越界检查。
a[10] = 1;
arr[10] = 1;
//arr不一定会报错,可能会在某一段进行报错。
  • This container is pretty useless. The only good thing is that the out-of-bounds inspection here is very strict ! Arrays will not necessarily report an error.

③Specialization of templates

  • Prerequisite: You must have a template to specialize the template.

1. Specialization of functor templates

#include<vector>
#include<iostream>
using namespace std;
template<class T>
struct less
{
    
    

	bool operator()(T n1, T n2)
	{
    
    
		return n1 < n2;
	}
};

struct A
{
    
    
	A(int a = 0)
		:_a(a)
	{
    
    }
	int _a;
};
//对A*进行特化
template<>
struct less<A*>
{
    
    

	bool operator()(A* n1, A* n2)
	{
    
    
		return n1->_a < n2->_a;
	}
};
int main()
{
    
    
	vector<A*> v;
	v.push_back(new A(5));
	v.push_back(new A(4));
	v.push_back(new A(3));
	v.push_back(new A(2));
	v.push_back(new A(1));
	
	sort(v.begin(), v.end(),less<A*>());
	
	for (auto e : v)
	{
    
    
	
		cout << e->_a << " ";
	}
	cout << endl;
	return 0;
}
  • Here, less is specialized to achieve functions that meet our requirements.
  • Here the function of sorting the content pointed to by the pointer is achieved.

2. Specialization of classes

template<class T1, class T2>
class Data
{
    
    
public:
	Data()
	{
    
    
		cout << "Data<T1,T2>" << endl;
	}
private:
	T1 n1;
	T2 n2;
};

semi-specialized

template<class T1>
class Data<T1*,char>
{
    
    
public:
	Data()
	{
    
    
		cout << "Data<T1,T2>" << endl;
	}
	T1* n1;
	char n2;
};

All specialization

template<class T1, class T2>
class Data<T1*,T2*>
{
    
    
public:
	Data()
	{
    
    
		cout << "Data<T1,T2>" << endl;
	}
	T1* n1;
	T2* n2;
};

template<>
class Data<int,double>
{
    
    

public:
	Data()
	{
    
    
		cout << "Data<int,double>" << endl;
	}
private:
	int n1 = 1;
	double n2 = 1.0;
};
  • Summary: Specialization is mainly to meet the needs of some special scenarios.

④Separation of template declaration and definition

  • stack.h
#include<iostream>
#include<deque>
using namespace std;
namespace my_STL
{
    
    
	template<class T ,class Con = deque<T>>
	class stack
	{
    
    
	public:

		void push(const T& val);
		void pop()
		{
    
    
			_st.pop_back();
		}
		T& top()
		{
    
    
			return _st[_st.size() - 1];
		}
		size_t size()
		{
    
    
			return _st.size();
		}
		bool empty()
		{
    
    
			return _st.empty();
		}
	private:
		Con _st;
	};
}
  • stack.cpp
#include"stack.h"
namespace my_STL
{
    
    
	template<class T, class Con>
	void stack<T,Con>::push(const T& val)
	{
    
    
		_st.push_back(val);
	}


}
  • test.cpp
void test4()
{
    
    
	my_STL::stack<int> st;
	st.push(1);
	st.push(2);
	st.push(3);
	//链接找不到,地址,因为在.cpp的文件的模板函数没有实例化!

	while (!st.empty())
	{
    
    
		cout << st.top() << " ";
		st.pop();
	}
	cout << endl;
}

What happens when linking again:
Insert image description here
Why is this?

answer:因为push没有实例化,所以找不到对应的地址。

How to solve: Display instantiation in test.cpp

	template
		class stack<int>;

Of course, this only treats the symptoms, not the root cause, so we usually put them in the same file.

Some file declarations and definitions in the library will be placed in files with the .hpp suffix to prevent this problem.

The template will be compiled twice, respectively:

1. Before instantiation, check the template code itself to see if the syntax is correct.

2. During instantiation, check the template code to see if all calls are valid.

Summarize

  • advantage
  1. Templates reuse code, save resources, and enable faster iterative development. This is why the C++ Standard Template Library (STL) was born.
  2. Enhanced code flexibility
  • defect
  1. Templates can lead to code bloat and longer compilation times (inevitable)
  2. When a template compilation error occurs, the error message is very messy and it is difficult to locate the error (the main problem).

Guess you like

Origin blog.csdn.net/Shun_Hua/article/details/130822089