![Object-Oriented Programming for Scientific Computing Template Metaprogramming](https://data.docslib.org/img/3a60ab92a6e30910dab9bd827208bcff-1.webp)
Object-Oriented Programming for Scientific Computing Template Metaprogramming Ole Klein Interdisciplinary Center for Scientific Computing Heidelberg University [email protected] 30. Juni 2015 Ole Klein (IWR) Object-Oriented Programming 30. Juni 2015 1 / 51 Calculating the Square Root We can calculate the square root as follows using nested intervals: #include<iostream> template<std::size_t N, std::size_t L=1, std::size_t H=N> struct Sqrt { public: enum{ mid = (L+H+1)/2 }; enum{ value = (N<mid*mid)? (std::size_t)Sqrt<N,L,mid-1>::value : (std::size_t)Sqrt<N,mid,H>::value }; }; template<std::size_t N, std::size_t M> struct Sqrt<N,M,M> { enum{ value = M }; }; int main() { std::cout<<Sqrt<9>::value<<" "<<Sqrt<42>::value<<std::endl; } Ole Klein (IWR) Object-Oriented Programming 30. Juni 2015 2 / 51 Template Instantiations • Calculating Sqrt<9> first leads to the execution of: Sqrt<9,1,9>::value = (9<25) ? Sqrt<9,1,4>::value : Sqrt<9,5,9>::value = Sqrt<9,1,4>::value As a result Sqrt<9,1,4> is calculated next: Sqrt<9,1,4>::value = (9<9) ? Sqrt<9,1,2>::value : Sqrt<9,3,4>::value = Sqrt<9,3,4>::value The next recursion step is then: Sqrt<9,3,4>::value = (9<16) ? Sqrt<9,3,3>::value : Sqrt<9,4,3>::value = Sqrt<9,3,3>::value = 3 • However, there is a problem with the ternary operator <condition>?<true-path>:<false-path>. The compiler generates not just the relevant part, but also the other that is not used. This means it has to expand the next recursion level on that side as well (although the result will be discarded), which results in the assembly of the complete binary tree. • For the square root of N this leads to 2N template instantiations. This puts a large strain on the resources of the compiler (both runtime and memory), and limits the scope of the technique. Ole Klein (IWR) Object-Oriented Programming 30. Juni 2015 3 / 51 Type Selection at Compile Time We can get rid of the unnecessary template instantiations by simply selecting the correct type and evaluating it directly. This can be carried out with a small metaprogram that corresponds to an if statement (also called a \compile time type selection"). // Definition including specialization for the true case template <b o o lB, typename T1, typename T2 > s t r u c t IfThenElse f t y p e d e f T1 ResultT; g ; // Partial specialization for the false case template <typename T1, typename T2> s t r u c t IfThenElse <f a l s e ,T1,T2> f t y p e d e f T2 ResultT; g ; #endif Ole Klein (IWR) Object-Oriented Programming 30. Juni 2015 4 / 51 Improved Calculation of the Square Root Using our meta-if statement we can implement the square root as follows: template <s t d : : s i z e t N, std::size t L=1, std::size t H=N> s t r u c t Sqrt f p u b l i c: enumf mid = (L+H+1)/2 g ; t y p e d e f typename IfThenElse <(N<mid∗mid ) , Sqrt<N, L , mid−1>, Sqrt<N, mid , H> >::ResultT ResultType; enumf value = ResultType:: value g ; g ; template <s t d : : s i z e t N, std::size t M> s t r u c t Sqrt<N,M,M> f enumf v a l u e = M g ; g ; This only requires about log2(N) template instantiations! Ole Klein (IWR) Object-Oriented Programming 30. Juni 2015 5 / 51 Turing Completeness of Template Metaprogramming Template meta programs may include: • State variables: the template parameters. • Loops: using recursion. • Conditional execution: using the ternary operator or template specialization (e.g. the meta-if). • Integer calculations. This is sufficient to perform any calculation, as long as there isn't any restriction on the number of recursive instantiations and on the number of state variables (which does not imply that it is useful to calculate everything with template metaprogramming) . Ole Klein (IWR) Object-Oriented Programming 30. Juni 2015 6 / 51 Loop Unrolling In the calculation of a scalar product, as in: template<typename T> inline T dot_product (int dim, T* a, T* b) { T result = T(); for(int i=0;i<dim;++i) { result += a[i]*b[i]; } return result; } the compiler often optimizes the computation for large arrays. However, if small scalar products of the type dp = dot_product(3,a,b); are used most of the time, then this may be written more efficiently. Ole Klein (IWR) Object-Oriented Programming 30. Juni 2015 7 / 51 Loop Unrolling // Primary template template<int DIM, typename T> class DotProduct { public: static T result (T* a, T* b) { return *a * *b + DotProduct<DIM-1,T>::result(a+1,b+1); } }; // Partial specialization as stopping criterion template<typename T> class DotProduct<1,T> { public: static T result (T* a, T* b) { return *a * *b; } }; Ole Klein (IWR) Object-Oriented Programming 30. Juni 2015 8 / 51 Loop Unrolling // for simplification template<int DIM, typename T> inline T dot_product (T* a, T* b) { return DotProduct<DIM,T>::result(a,b); } Ole Klein (IWR) Object-Oriented Programming 30. Juni 2015 9 / 51 Application: Loop Unrolling #include <iostream> #include"loop_unrolling.h" int main() { int a[3] = { 1, 2, 3}; int b[3] = { 5, 6, 7}; std::cout <<"dot_product<3>(a,b) = " << dot_product<3>(a,b) <<'\n'; std::cout <<"dot_product<3>(a,a) = " << dot_product<3>(a,a) <<'\n'; } Ole Klein (IWR) Object-Oriented Programming 30. Juni 2015 10 / 51 Loop Unrolling for Random Access Container // Primary template template<int DIM, typenameT, template<typename U,typename=std::allocator<U> > class vect> struct DotProduct { static T result(const vect<T> &a, const vect<T> &b) { return a[DIM-1]*b[DIM-1] + DotProduct<DIM-1,T,vect>::result(a,b); } }; // Partial specialization as stopping criterion template<typenameT, template<typename U,typename=std::allocator<U> > class vect> struct DotProduct<1,T,vect> { static T result(const vect<T> &a, const vect<T> &b) { return a[0] * b[0]; } }; Ole Klein (IWR) Object-Oriented Programming 30. Juni 2015 11 / 51 Loop Unrolling for Random Access Container // For simplification template<int DIM, typenameT, template<typename U,typename=std::allocator<U> > class vect> inline T dot_product(const vect<T> &a, const vect<T> &b) { return DotProduct<DIM,T,vect>::result(a,b); } Ole Klein (IWR) Object-Oriented Programming 30. Juni 2015 12 / 51 Application: Loop Unrolling for Random Access Container #include <iostream> #include <vector> #include"loop_unrolling2.h" int main() { std::vector<double> a(3,3.0); std::vector<double> b(3,5.0); std::cout <<"dot_product<3>(a,b) = " << dot_product<3>(a,b) <<'\n'; std::cout <<"dot_product<3>(a,a) = " << dot_product<3>(a,a) <<'\n'; } Ole Klein (IWR) Object-Oriented Programming 30. Juni 2015 13 / 51 constexpr #include<iostream> int x1 = 7; constexpr int x2 = 7; constexpr int x3 = x1;// Error, x1 is nota constexpr constexpr int x4 = x2; constexpr int Fac(int n) { return n<2 ? 1 : n * Fac(n-1); } int main() { std::cout << Fac(10) << std::endl; } • C++11 introduces a simple alternative to template metaprogramming: expressions that are already evaluated at compile time. • In a constexpr only variables or functions which are constexpr themselves may be used. Ole Klein (IWR) Object-Oriented Programming 30. Juni 2015 14 / 51 constexpr It must be possible to evaluate a constexpr at compile time: void f(int n) { constexpr int x = Fac(n);// Error,n isn't known at // time of translation int f10 = Fac(10);// Correct } int main() { const int ten = 10; int f10 = Fac(10);// Also correct } Ole Klein (IWR) Object-Oriented Programming 30. Juni 2015 15 / 51 constexpr This will work even for objects of classes whose constructor is simple enough to be defined as constexpr: struct Point { int x,y; constexpr Point(int xx, int yy) : x(xx), y(yy) {} }; constexpr Point origo(0,0); constexpr int z = origo.x; constexpr Point a[] = {Point(0,0), Point(1,1), Point(2,2)}; constexpr x = a[1].x;//x becomes1 • constexpr functions may not have the return type void and neither variables nor functions may be defined within them (this also applies to constexpr constructors). • The function body can only contain declarations and a single return statement. Ole Klein (IWR) Object-Oriented Programming 30. Juni 2015 16 / 51 Example: Numbers with Units Template Metaprogramming Example: Numbers with Units • When performing calculations with physical quantities errors may occur. • The worst case scenario is comparing apples and oranges. • The aim is the construction of a class which allows calculating with units. • The implementation uses template metaprogramming. All calculations (except the conversion for input and output) are as fast as without units. • The necessary tests are performed at compile time and automatically optimized out. Ole Klein (IWR) Object-Oriented Programming 30. Juni 2015 17 / 51 Example: Numbers with Units Units: Unit Class We first introduce a template class for units: template<intM, intK, int S> struct Unit { enum { m=M, kg=K, s=S }; }; using M = Unit<1,0,0>;// Meters using Kg = Unit<0,1,0>;// Kilogram using S = Unit<0,0,1>;// Seconds using MpS = Unit<1,0,-1>;// Meter per second(m/s) using MpS2 = Unit<1,0,-2>;// Meter per second squared(m/(s*s)) Ole Klein (IWR) Object-Oriented Programming 30. Juni 2015 18 / 51 Example: Numbers with Units Enum Classes enum {RED, GREEN, BLUE};//C enum enum class Color {RED, GREEN, BLUE};//C++11 enum • In C enums are simply integer values with a name. • The same name may be used only once.
Details
-
File Typepdf
-
Upload Time-
-
Content LanguagesEnglish
-
Upload UserAnonymous/Not logged-in
-
File Pages51 Page
-
File Size-