[C + +] advanced template

1. Non type template parameters

  • Type template parameter: the type name that appears in the template parameter list and follows class or typename
  • Non type template parameter: use a constant as a parameter of the class (function) template. In the class (function) template, the parameter can be used as a constant

Non type template parameter code demonstration

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;
    };

be careful:

① Floating point numbers, class objects, and strings are not allowed as non type template parameters

② The value of non type template parameters must be confirmed at compile time

2. Specialization of formwork

  • When you use a template, you can generate some errors that are not related to the implementation of the template
  • Template specialization: the implementation method of specialization for special types based on the original template class.
  • Classification of template specialization: function template specialization and class template specialization

The template does not specialize in code validation that may produce incorrect results

#include <iostream>
using namespace std;
template<class T>
bool Max(T& left, T& right)
{
	return left > right;
}

void Test()
{
	char* p1 = "world";
	char* p2 = "hello";
	if (Max(p1, p2))
		cout << p1 << endl;
	else
		cout << p2 << endl;
}
int main()
{
	Test();
	system("pause");
	return 0;
}

Output result: hello

Normally, world should be output, but hello is output, indicating that the template is invalid for char * type

2.1 function template specialization

Function template specialization steps:

  1. You must have 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

Function template specialization code demonstration

#include <iostream>
using namespace std;
template<class T>
bool Max(T& left, T& right)
{
	return left > right;
}

// Function template specialization
template<>
bool Max<char*>(char*& left, char*& right)
{
	if (strcmp(left, right) > 0)
		return true;
	return false;
}

void Test()
{
	char* p1 = "world";
	char* p2 = "hello";
	if (Max(p1, p2))
		cout << p1 << endl;
	else
		cout << p2 << endl;
}
int main()
{
	Test();
	system("pause");
	return 0;
}

Output result: world

Interpretation: the Max function invoked in the Test function, where the Max function follows the logic of specialization, thus dealing with the special type of failure.

You can also use this type of function instead of function template specialization

bool Max(char*& left, char*& right)
{
	if (strcmp(left, right) > 0)
		return true;
	return false;
}

Type 2.2 template specialization

2.2.1 full specialization

  • Full specialization: specify all parameters in the template parameter list as specific types

Full specialization code demonstration

template<class T1, class T2>	// 1
    class Data
    {
        public:
        Data() {cout<<"Data<T1, T2>" <<endl;}
        private:
        T1 _d1;
        T2 _d2;
    };

template<> 		//2
class Data<int, char>
{
    public:
    Data() {cout<<"Data<int, char>" <<endl;}
    private:
    T1 _d1;
    T2 _d2;
};

void Test()
{
    Data<int, int> d1;	//1
    Data<int, char> d2;	//2
}

2.2.2 partial specialization

  • Partial specialization: specify some parameters in the template parameter list as specific types

Partial specialization code demonstration

template<class T1, class T2>	// 1
class Data
{
    public:
    Data() {cout<<"Data<T1, T2>" <<endl;}
    private:
    T1 _d1;
    T2 _d2;
};

// Specialize the second parameter to int
template <class T1>		// 2
class Data<T1, int>
{
    public:
    Data() {cout<<"Data<T1, int>" <<endl;}
    private:
    T1 _d1;
    int _d2;
};

void Test()
{
	Data<int, int> d1;	//2
	Data<int, char> d2;	//1
}

Another way of partial specialization

//The two parameters are partial specialization to pointer type
template <typename T1, typename T2>
class Data <T1*, T2*>
{
    public:
    Data() {cout<<"Data<T1*, T2*>" <<endl;}
    private:
    T1 _d1;
    T2 _d2;
};

//The two parameters are partially specialized into reference types
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;
        }
    private:
    const T1 & _d1;
    const T2 & _d2;
};

void test()
{
    Data<int *, int*> d3; // Call specialized pointer version
    Data<int&, int&> d4(1, 2); // Call specialized pointer version
}

2.3 types of template specific applications

  • Built in types can be distinguished from custom types
  • Use TrueType for built-in types
  • False type represents a custom type
  • Define TypeTraits and alias all types IsPODType
  • If it is a built-in type, call the corresponding class template specialization
  • If it is customized, get F for shallow copy Type, and assign value through loop for deep copy
#include <string.h>
#pragma warning(disable:4996)
using namespace std;

// Gets whether the type is a built-in type or a custom type
struct TrueType
{
	static bool Get()
	{
		return true;
	}
};
struct FalseType
{
	static bool Get()
	{
		return false;
	}
};

// template specialization 
template<class T>
struct TypeTraits
{
	typedef FalseType PODTYPE;
};

template<>
struct TypeTraits<int>
{
	typedef TrueType PODTYPE;
};
template<>
struct TypeTraits<double>
{
	typedef TrueType PODTYPE;
};
template<>
struct TypeTraits<short>
{
	typedef TrueType PODTYPE;
};
template<>
struct TypeTraits<float>
{
	typedef TrueType PODTYPE;
};
template<>
struct TypeTraits<long>
{
	typedef TrueType PODTYPE;
};

class String
{
public:
	String(const char* str = "")
	{
		if (nullptr == str)
			str = "";

		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
	String(const String& s)
		: _str(new char[strlen(s._str) + 1])
	{
		strcpy(_str, s._str);
	}
	String& operator=(const String& s)
	{
		if (this != &s)
		{
			char* temp = new char[strlen(s._str) + 1];
			strcpy(temp, s._str);
			delete[] _str;
			_str = temp;
		}

		return *this;
	}

	~String()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};



template<class T>
void Copy(T* dst, const T* src, size_t size, TrueType)
{
	memcpy(dst, src, size*sizeof(T));
}
template<class T>
void Copy(T* dst, const T* src, size_t size, FalseType)
{
	for (size_t i = 0; i < size; ++i)
		dst[i] = src[i];
}
template<class T>
void Copy(T* dst, const T* src, size_t size)
{
	Copy(dst, src, size, TypeTraits<T>::PODTYPE());
}

int main()
{
	int array1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	int array2[sizeof(array1) / sizeof(array1[0])];
	Copy(array2, array1, 10);

	String s1[] = { "1111", "2222", "3333" };
	String s2[3];
	Copy(s2, s1, 3);

	return 0;
}

3. Template separation compilation

3.1 what is separate compilation

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

3.1.1 separate compilation of code without template

When the following three files exist in a project in VS2013 at the same time, they can be linked correctly through compilation

//test.h
void f();//Here we declare a function f


//test.cpp
#include "test.h"
void f()
{
	//TO DO
}  //Test. Is implemented here f function declared in H


//main.cpp
#include "test.h"
int main()
{
	f(); //Call f, which has an external connection type
	return 0;
}

Explanation of cause

  • In this example, test CPP and main CPP is compiled into different Obj file (test.obj and main.obj)
  • In main In CPP, the F function is called, but when the compiler compiles main.cpp, all it knows is main Test contained in CPP H file about void f(); Therefore, the compiler regards f here as an external connection type, that is, its function implementation code is in another In obj file, this example is test Obj, that is, main Obj doesn't actually have even one line of binary code about the F function, and these codes actually exist in test CPP compiled test Obj.
  • In main The call to f in obj will only generate one line of call instruction, as follows
00C7140E  call        f (0C710E6h)  //The address of f here is at test Function address in obj
  • At compile time, the call instruction is obviously wrong because main There is not a single line of implementation code for f in obj. So what?
  • This is the task of the connector, which is responsible for other functions obj (test.obj in this example) looks for the implementation code of F. after finding it, replace the call address of the call f instruction with the actual function entry point address of F.
  • It should be noted that the connector will actually be used in the project obj connection has become a exe file, and its most critical task is to find an external connection symbol in another obj, and then replace the original "false" address.

Summary: in the case of separate compilation without template code, if there is no implementation of a function in the main source file, the main source file will hope to find it in other obj files. The search task is handed over to the connector, which will find the implementation of the function in other obj files

3.1.2 separate compilation with template code

Due to the template code, the compiler will not instantiate the unused template code, so the entry address of function f cannot be found, so it can be compiled but not connected

//test.h
template<class T>
class A
{
public:
	void f(); // This is just a statement
};


//test.cpp
#include "test.h"
template<class T>
void A<T>::f()  // Implementation of template
{
  // TO DO
}


//main.cpp
#include "test.h"
int main()
{
    A<int> a;
    a.f(); 	// #1
    
    A<double> b;
    b.f();	// #2
}

report errors

error	2	error LNK2019: Unresolved external symbol "public: void __thiscall A<double>::f(void)" (?f@?$A@N@@QAEXXZ),The symbol is in the function _main Referenced in
 error	1	error LNK2019: Unresolved external symbol "public: void __thiscall A<int>::f(void)" (?f@?$A@H@@QAEXXZ),The symbol is in the function _main Referenced in

Explanation of cause

  • The compiler does not know the definitions of a < int >:: F and a < double >:: f * * at * * #1 and #2 because it is not in test H, so the compiler has to hope that the connector can be used in other Find an instance of a < int >:: F in obj. In this case, it is test Obj, is there really binary code of a < int >:: F in the latter?
  • NO!!! Because the C + + standard clearly states that when a template is not used, it should not be instantiated, test Is a < int >:: F used in CPP? No, So actually test CPP compiled test There is no binary code about A::f in obj file, so the connector is stupid and has to give a connection error.
  • However, if in test CPP writes a function, where A<int>:: F, the compiler will turn out the example, because at this point (test.cpp) middle note, the compiler knows the definition of the template, so it can be instantiated, so test.obj's symbol export table has the address of a < int >:: F, so the connector can complete the task at * * #1, but the connector still can't find the address of the instantiated function (a < double >:: F) at #2 * *, because it hasn't been in test Used in CPP, so it will not appear by its instantiation function, so the address of a < double >:: F symbol will not exist

Summary: in the case of separate compilation with template code, if the implementation and definition are separated, and the call and implementation are also separated, the template code may not be instantiated, which will lead to connection errors and unable to generate executable files

3.2 problem solving methods

  1. Put both the ". hpp" and "HPH" declarations in one file
  2. The location of the template definition is explicitly instantiated

4. Template summary

[advantages]

  1. The template reuses the code, saves resources and faster iterative development
  2. Enhanced code flexibility

[disadvantages]

  1. Templates can cause code bloat problems and lead to longer compilation times
  2. When a template compilation error occurs, the error information is very messy and it is difficult to locate the error

Tags: C++

Posted by sw9 on Mon, 18 Apr 2022 05:47:30 +0930