(C + + template programming): mixed meta programming

catalogue

Hybrid meta programming

Example of conventional point product calculation

Calculation of dot product by mixed element programming

Two stages of C + + compilation

Intelligent generation and expansion of code

Hybrid meta programming

[concept introduction]

  • (a) The programmer writes a piece of metaprogramming code
  • (b) The compiler compiles (generates) a new code according to this code. The real function of the program is the generated new code.
  • (c) The compiler will compile this new code to produce the final executable program.
  • Hybrid metaprogramming can be regarded as the generator of C + + code at run time.

Example of conventional point product calculation

  • a) Array a has three elements, a [0], a [1], and a [2], with values of 1, 2, and 3
  • b) Array B has three elements, b[0],b[1],b[2], with values of 4, 5 and 6 respectively
  • c) The dot product of a and b is a numerical value. The result is: a[0]*b[0] + a[1]*b[1] + a[2]*b[2] = 1 * 4 + 2 * 5 + 3 * 6 = 32
template <typename T,int U> //T: Element type, U: array size
auto DotProduct(T* array1, T *array2)
{
	T dpresult = T{}; //Zero initialization
	for (int i = 0; i < U; ++i)
	{
		dpresult += array1[i] * array2[i];
	}
	return dpresult;
}
  • call
int a[] = { 1,2,3 };
int b[] = { 4,5,6 };
int result = DotProduct<int, 3>(a, b);
cout << result << endl;
  • Check the disassembly code and call dotproduct < int, 3 > to realize the dot product operation of array a and array b

[add inline]

template <typename T,int U> //T: Element type, U: array size
inline auto DotProduct(T* array1, T *array2)
{
	T dpresult = T{}; //Zero initialization
	for (int i = 0; i < U; ++i)
	{
		dpresult += array1[i] * array2[i];
	}
	return dpresult;
}
  • call
int a[] = { 1,2,3 };
int b[] = { 4,5,6 };
int result = DotProduct<int, 3>(a, b);
cout << result << endl;
  • Check the disassembly code. A series of assembly codes generated after DotProduct is inline are replaced by functions instead of calling the function ontology, so the compiler does not need to instantiate DotProduct < int, 3 >

  • After the function call statement is replaced by the function ontology, because there is a for loop statement in the template, it is necessary to judge whether to end the loop condition, etc. Accordingly, there will also be judgment jump statements in the assembly code, which will affect the execution efficiency of the program.

Calculation of dot product by mixed element programming

[example]

//Generalized version
template <typename T,int U>//T: Element type, U: array size
struct DotProduct
{
	static T result(const T* a, const T* b)
	{
		return (*a) * (*b) + DotProduct<T, U - 1>::result(a + 1, b + 1);
	}
};

//The specialized version is used for the exit of recursive call
template <typename T>
struct DotProduct<T, 0>
{
	static T result(const T* a, const T* b)
	{
		return T{};
	}
};
  • call
int a[] = { 1,2,3 };
int b[] = { 4,5,6 };
int result = DotProduct<int, 3>::result(a, b);
    //The compiler generates (* a) * (*b) + (*(a+1)) * (*(b+1)) + (*(a+2)) * (*(b+2)) after compilation 
    //int result =  (*a) * (*b) + (*(a+1)) * (*(b+1)) + (*(a+2)) * (*(b+2)) ;
cout << result << endl;
  • analysis
DotProduct<int,3>Instantiated, DotProduct<int,2>Instantiated, DotProduct<int,1>Instantiated, DotProduct<int, 0>Instantiated
DotProduct<int, 3>::result(a, b) = 
(*a) * (*b) + DotProduct<int, 2>::result(a + 1, b + 1) =
(*a) * (*b) + (*(a+1)) * (*(b+1)) + DotProduct<int, 1>::result(a + 2, b + 2) =
(*a) * (*b) + (*(a+1)) * (*(b+1)) + (*(a+2)) * (*(b+2)) +DotProduct<int, 0>::result(a + 3, b + 3) = 
(*a) * (*b) + (*(a+1)) * (*(b+1)) + (*(a+2)) * (*(b+2)) + 0
  • View disassembly code

Two stages of C + + compilation

[divide the whole compilation process into two stages: early stage and late stage]

  • Early stage: the C + + compiler in this stage actually acts as an interpreter and directly interprets and executes the C + + source code (metaprogramming code) developed by the programmer. The work result of this stage is to produce a series of C + + code (so metaprogramming is regarded as the runtime C + + code generator).
  • Later stage: the C + + compiler in this stage restores the functions that the C + + compiler should have -- compile and link the resulting code generated in the previous stage, and finally generate an executable program.

Intelligent generation and expansion of code

  • If the following call is made for the above mixed programming dot product
int a[] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
int b[] = { 4,5,6,7,8,9,1,2,3,10,11,12 };
int result = DotProduct<int, 12>::result(a, b); 
//int result = (*a) * (*b) + (*(a + 1)) * (*(b + 1)) + (*(a + 2)) * (*(b + 2)) + .........+ (*(a + 11)) * (*(b + 11)) 
cout << result << endl;
  • View its disassembly code

  • Through dumpbin, the compiler instantiates the following functions:
DotProduct<int, 0>,DotProduct<int, 1>,DotProduct<int, 2>....DotProduct<int, 12>

DotProduct<int,4>::result ,DotProduct<int,7>::result ,DotProduct<int,10>::result 
  • Combined with the disassembly code, it is found that relevant calls are made to the function, which is not like the case of U = 3 (new code is generated and executed, and the function is not called).
  • VS2019 compiler already has considerable intelligence in generating a series of C + + code for meta programming - avoiding excessive and lengthy C + + code through some function calls.
  • Dotproduct < int, 12 >:: result (a, b) causes code expansion (the size of the finally generated executable program will become larger), which should be paid attention to during programming.

Posted by fluvius on Sat, 07 May 2022 01:04:45 +0300