Download
practical meta programming n.
Skip this Video
Loading SlideShow in 5 Seconds..
Practical Meta-programming PowerPoint Presentation
Download Presentation
Practical Meta-programming

Practical Meta-programming

169 Views Download Presentation
Download Presentation

Practical Meta-programming

- - - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript

  1. Practical Meta-programming By Reggie Meisler

  2. Topics • How it works in general • Useful practices • Type Traits(Already have slides on that) • Math Functions (Fibonacci, Dot Prod, Sine) • Static Type Ids • Tuples • SFINAE

  3. How it works in general • All based around template specialization and partial template specialization mechanics • Also based on the fact that we can recursively instantiate template classes with about 500 levels of depth • Conceptually analogous to functional programming languages • Can only operate on types and immutable data • Data is never modified, only transformed • Iteration through recursion

  4. Template mechanics • Template specialization rules are simple • When you specialize a template class, that specialization now acts as a higher-priority filter for any types (or integral values) that attempt to instantiate the template class

  5. Template mechanics template <typename T>classMyClass { /*…*/ }; // Full specializationtemplate <>classMyClass<int> { /*…*/ };// Partial specializationtemplate <typename T>classMyClass<T*> { /*…*/ };

  6. Template mechanics MyClass<float> goes here template <typename T>classMyClass { /*…*/ }; // Full specializationtemplate <>classMyClass<int> { /*…*/ };// Partial specializationtemplate <typename T>classMyClass<T*> { /*…*/ }; MyClass<int> goes here MyClass<int*> goes here

  7. Template mechanics • This filtering mechanism of specialization and partial specialization is like branching at compile-time • When combined with recursive template instantiation, we’re able to actually construct all the fundamental components of a programming language

  8. How it works in general // Example of a simple summationtemplate <int N>struct Sum{// Recursive call!static const intvalue = N + Sum<N-1>::value;};// Specialize a base case to end recursion!template <>struct Sum<1>{static const intvalue = 1;};// Equivalent to ∑(i=1 to N)i

  9. How it works in general // Example of a simple summationintmySum = Sum<10>::value;// mySum = 55 = 10 + 9 + 8 + … + 3 + 2 + 1

  10. How it works in general // Example of a type trait that checks for consttemplate <typename T>structIsConst{static const boolvalue = false;};// Partially specialize for <const T>template <typename T>structIsConst<const T>{static const bool value = true;};

  11. How it works in general // Example of a type trait that checks for constbool amIConst1 = IsConst<const float>::value;bool amIConst2 = IsConst<unsigned>::value; // amIConst1 = true// amIConst2 = false

  12. Type Traits • Already have slides on how these work(Go to C++/Architecture club moodle) • Similar to IsConst example, but also allows for type transformations that remove or add qualifiers to a type, and deeper type introspection like checking if one type inherits from another • Later in the slides, we’ll talk about SFINAE, which is considered to be a very powerful type trait

  13. Math • Mathematical functions are by definition, functional. Some input is provided, transformed by some operations, then we’re given an output • This makes math functions a perfect candidate for compile-time precomputation

  14. Fibonacci template <intN> // Fibonacci functionstruct Fib{static const int value = Fib<N-1>::value + Fib<N-2>::value;};template <>struct Fib<0> // Base case: Fib(0) = 1{static const int value = 1;};template <>struct Fib<1> // Base case: Fib(1) = 1{static const int value = 1;};

  15. Fibonacci • Now let’s use it!// Print out 42 fib valuesfor( inti = 0; i < 42; ++i )printf(“fib(%d) = %d\n”, i, Fib<i>::value); • What’s wrong with this picture?

  16. Real-time vs Compile-time • Oh crap! Our function doesn’t work with real-time variables as inputs! • It’s completely impractical to have a function that takes only literal values • We might as well just calculate it out and type it in, if that’s the case!

  17. Real-time vs Compile-time • Once we create compile-time functions, we need to convert their results into real-time data • We need to drop all the data into a table (Probably an array for O(1) indexing) • Then we can access our data in a practical manner (Using real-time variables, etc)

  18. Fibonacci Table intFibTable[ MAX_FIB_VALUE ]; // Our tabletemplate <int index = 0>structFillFibTable{static void Do() {FibTable[index] = Fib<index>::value;FillFibTable<index + 1>::Do(); // Recursive loop, unwinds at compile-time }};// Base case, ends recursion at MAX_FIB_VALUE template <>structFillFibTable<MAX_FIB_VALUE>{static void Do() {}};

  19. Fibonacci Table • Now our Fibonacci numbers can scale based on the value of MAX_FIB_VALUE, without any extra code • To build the table we can just start the template recursion like so:FillFibTable<>::Do(); • The template recursion should compile into code equivalent to:FibTable[0] = 1;FibTable[1] = 1; // etc… until MAX_FIB_VALUE

  20. Using Fibonacci // Print out 42 fib valuesfor( inti = 0; i < 42; ++i )printf(“fib(%d) = %d\n”, i, FibTable[i]);// Output:// fib(0) = 1// fib(1) = 1// fib(2) = 2// fib(3) = 3// …

  21. The Meta Tradeoff • Now we can quite literally see the tradeoff for meta-programming’s magical O(1) execution time • A classic memory vs speed problem • Meta, of course, favors speed over memory • Which is more important for your situation?

  22. Compile-time recursive function calls • Similar to how we unrolled our loop for filling the Fibonacci table, we can unroll other loops that are usually placed in mathematical calculations to reduce code size and complexity • As you’ll see, this increases the flexibility of your code while giving you near-hard-coded performance

  23. Dot Product template <typenameT, int Dim>structDotProd{static T Do(const T* a, const T* b) {// Recurse (Ideally unwraps to the hard-coded equivalent in assembly)return (*a) * (*b) + DotProd<T, Dim – 1>::Do(a + 1, b + 1); }};// Base case: end recursion at single element vector dot prodtemplate <typename T>structDotProd<T, 1>{static T Do(const T* a, const T* b) {return (*a) * (*b); }};

  24. Dot Product Always take advantage ofauto-type detection! // Syntactic sugartemplate <typename T, int Dim>T DotProduct(T (&a)[Dim], T (&b)[Dim]){return DotProd<T, Dim>::Do(a, b);}// Example usefloat v1[3] = { 1.0f, 2.0f, 3.0f };float v2[3] = { 4.0f, 5.0f, 6.0f };DotProduct(v1, v2); // = 32.0f

  25. Dot Product // Other possible method, assuming POD vector// * Probably more practicaltemplate <typename T>floatDotProduct(const T& a, const T& b){static const size_tDim = sizeof(T)/sizeof(float);return DotProd<float, Dim>::Do((float*)&a, (float*)&b);}

  26. Dot Product // Other possible method, assuming POD vector// * Probably more practicaltemplate <typename T>floatDotProduct(const T& a, const T& b){static const size_tDim = sizeof(T)/sizeof(float);return DotProd<float, Dim>::Do((float*)&a, (float*)&b);} We can auto-determine the dimension based on size since T is a POD vector

  27. Approximating Sine • Sine is a function we’d usually like to approximate for speed reasons • Unfortunately, we’ll only get exact values on a degree-by-degree basis • Because sine technically works on an uncountable set of numbers (Real Numbers)

  28. Approximating Sine template <int degrees>struct Sine{ static const float radians; static const float value;};template <int degrees>const float Sine<degrees>::radians = degrees*PI/180.0f;// x – x3/3! + x5/5! – x7/7! (A very good approx)template <int degrees>constfloat Sine<degrees>::value =radians - ((radians*radians*radians)/6.0f) + ((radians*radians*radians*radians*radians)/120.0f) –((radians*radians*radians*radians*radians*radians*radians)/5040.0f);

  29. Approximating Sine Floats can’t be declared inside the template class template <int degrees>struct Sine{ static const float radians; static const float value;};template <int degrees>const float Sine<degrees>::radians = degrees*PI/180.0f;// x – x3/3! + x5/5! – x7/7! (A very good approx)template <int degrees>constfloat Sine<degrees>::value =radians - ((radians*radians*radians)/6.0f) + ((radians*radians*radians*radians*radians)/120.0f) –((radians*radians*radians*radians*radians*radians*radians)/5040.0f); Need radians for Taylor Series formula Our approximated result

  30. Approximating Sine • We’ll use the same technique as shown with the Fibonacci meta function for generating a real-time data table of Sine values from 0-359 degrees • Instead of accessing the table for its values directly, we’ll use an interface function • We can just interpolate any in-between degree values using our table constants

  31. Final Result: FastSine // Approximates sine, favors ceil valuefloatFastSine(float radians){ // Convert to degreesfloat degrees = radians * 180.0f/PI;unsigned approxA = (unsigned)degrees;unsigned approxB = (unsigned)ceil(degrees); float t = degrees - approxA; // Wrap degrees, use linear interp and index SineTablereturn t * SineTable[approxB % 360] + (1-t) * SineTable[approxA % 360];}

  32. Tuples • Ever want a heterogeneous container? You’re in luck! A Tuple is simple, elegant, sans polymorphism, and 100% type-safe! • A Tuple is a static data structure defined recursively by templates

  33. Tuples structNullType {}; // Empty structuretemplate <typename T, typename U = NullType>structTuple{typedefT head;typedefU tail; T data;U next;};

  34. Making a Tuple This is what I mean by “recursively defined” typedefTuple<int,Tuple<float,Tuple<MyClass>>>MyType;MyType t;t.data// Element 1t.next.data// Element 2t.next.next.data// Element 3

  35. Tuple in memory Tuple<int, Tuple<float, Tuple<MyClass>>> data:int next: Tuple<float, Tuple<MyClass>> data: float next: Tuple<MyClass> data: MyClass next: NullType

  36. Tuple<int, Tuple<float, Tuple<MyClass>>> data:int next Tuple<float, Tuple<MyClass>> data: float next Tuple<MyClass> data: MyClass next NullType

  37. Better creation template <typename T1 = NullType, typename T2 = NullType, …>structMakeTuple;template <typename T1>structMakeTuple<T1, NullType, …> // Tuple of one type{typedefTuple<T1> type;};template <typename T1, typename T2>structMakeTuple<T1, T2, …> // Tuple of two types{typedefTuple<T1, Tuple<T2>> type;};// Etc… Not the best solution, but simplifies syntax

  38. Making a Tuple Pt 2 typedefMakeTuple<int, float, MyClass>MyType;MyType t;t.data// Element 1t.next.data// Element 2t.next.next.data// Element 3 Better But can we do something about this indexing mess?

  39. Better indexing It’s a good thing we made those typedefs template <int index>structGetValue{template <typenameTList>statictypenameTList::head& From(TList& list) {returnGetValue<index-1>::From(list.next); // Recurse }};template <>structGetValue<0> // Base case: Found the list data{template <typenameTList>statictypenameTList::head& From(TList& list) { return list.data; }}; Making use of template function auto-type detection again

  40. Better indexing // Just to sugar up the syntax a bit#defineTGet(list, index) \ GetValue<index>::From(list)

  41. Delicious Tuple MakeTuple<int, float, MyClass> t;// TGet works for both access and mutationTGet(t, 0) // Element 1TGet(t, 1) // Element 2TGet(t, 2) // Element 3

  42. Tuple • There are many more things you can do with Tuple, and many more implementations you can try (This is probably the simplest) • Tuples are both heterogeneous containers, as well as recursively-defined types • This means there are a lot of potential uses for them • Consider how this might be used for messaging or serialization systems

  43. SFINAE(Substitution Failure Is Not An Error) • What is it? A way for the compiler to deal with this:structMyType { typedefinttype; };// Overloaded template functionstemplate <typename T>voidfnc(T arg);template <typename T>voidfnc(typenameT::type arg);void main(){fnc<MyType>(0); // Calls the second fncfnc<int>(0); // Calls the first fnc (No error)}

  44. SFINAE(Substitution Failure Is Not An Error) • When dealing with overloaded function resolution, the compiler can silently rejectill-formed function signatures • As we saw in the previous slide, intwas ill-formed when matched with the function signature containing, typenameT::type, but this did not cause an error

  45. Does MyClass have an iterator? // Define types of different sizes typedeflong Yes;typedefshort No;template <typename T>Yes fnc(typename T::iterator*); // Must be pointer!template <typename T>No fnc(…); // Lowest priority signaturevoid main(){// Sizeof check, can observe types without calling fncprintf(“Does MyClass have an iterator? %s \n”,sizeof(fnc<MyClass>(0)) == sizeof(Yes) ? “Yes” : “No”);}

  46. Nitty Gritty • We can use sizeof to inspect the return value of the function without calling it • We pass the overloaded function 0(A null ptr to type T) • If the function signature is not ill-formed with respect to type T, the null ptr will be less implicitly convertible to the ellipses

  47. Nitty Gritty • Ellipses are SO low-priority in terms of function overload resolution, that any function that even stands a chance of working (is not ill-formed) will be chosen instead! • So if we want to check the existence of something on a given type, all we need to do is figure out whether or not the compiler chose the ellipses function

  48. Check for member function // Same deal as before, but now requires this struct// (Yep, member function pointers can be template// parameters)template <typename T, T& (T::*)(const T&)>structSFINAE_Helper;// Does my class have a * operator?// (Once again, checking w/ pointer)template <typename T>Yes fnc(SFINAE_Helper<T, &T::operator*>*);template <typename T>No fnc(…);

  49. Nitty Gritty • This means we can silently inspect any public member of a given type at compile-time! • For anyone who was disappointed about C++0x dropping concepts, they still have a potential implementation in C++ through SFINAE

  50. Questions?