1 / 50

Practical Meta-programming

Practical Meta-programming. By Reggie Meisler. 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. How it works in general.

elvina
Download Presentation

Practical Meta-programming

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

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?

More Related