Your SlideShare is downloading. ×
Practical Meta Programming
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Saving this for later?

Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime - even offline.

Text the download link to your phone

Standard text messaging rates apply

Practical Meta Programming

1,613
views

Published on


0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
1,613
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
29
Comments
0
Likes
1
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Practical Meta-programming By Reggie Meisler
  • 2. Topics
  • 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> class MyClass { /*…*/ }; // Full specialization template <> class MyClass< int > { /*…*/ }; // Partial specialization template < typename T> class MyClass<T*> { /*…*/ };
  • 6. Template mechanics
    • template < typename T> class MyClass { /*…*/ }; // Full specialization template <> class MyClass< int > { /*…*/ }; // Partial specialization template < typename T> class MyClass<T*> { /*…*/ };
    MyClass<float> goes here 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 summation template < int N> struct Sum { // Recursive call! static const int value = N + Sum<N-1>::value; }; // Specialize a base case to end recursion! template <> struct Sum<1> { static const int value = 1; }; // Equivalent to ∑ (i=1 to N) i
  • 9. How it works in general
    • // Example of a simple summation int mySum = 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 const template < typename T> struct IsConst { static const bool value = false ; }; // Partially specialize for <const T> template < typename T> struct IsConst< const T> { static const bool value = true ; };
  • 11. How it works in general
    • // Example of a type trait that checks for const bool 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 < int N> // Fibonacci function struct 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 values for ( int i = 0; i < 42; ++i ) printf( “fib(%d) = %d ” , 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
    • int FibTable[ MAX_FIB_VALUE ]; // Our table template < int index = 0> struct FillFibTable { 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 <> struct FillFibTable<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 values for ( int i = 0; i < 42; ++i ) printf( “fib(%d) = %d ” , 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 < typename T, int Dim> struct DotProd { 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 prod template < typename T> struct DotProd<T, 1> { static T Do( const T* a, const T* b) { return (*a) * (*b); } };
  • 24. Dot Product
    • // Syntactic sugar template < typename T, int Dim> T DotProduct(T (&a)[Dim], T (&b)[Dim]) { return DotProd<T, Dim>::Do(a, b); } // Example use float v1[3] = { 1.0f, 2.0f, 3.0f }; float v2[3] = { 4.0f, 5.0f, 6.0f }; DotProduct(v1, v2); // = 32.0f
    Always take advantage of auto-type detection!
  • 25. Dot Product
    • // Other possible method, assuming POD vector // * Probably more practical template < typename T> float DotProduct( const T& a, const T& b) { static const size_t Dim = sizeof (T)/ sizeof ( float ); return DotProd< float , Dim>::Do(( float *)&a, ( float *)&b); }
  • 26. Dot Product
    • // Other possible method, assuming POD vector // * Probably more practical template < typename T> float DotProduct( const T& a, const T& b) { static const size_t Dim = 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 – x 3 /3! + x 5 /5! – x 7 /7! (A very good approx) template < int degrees> const float 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
    • 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 – x 3 /3! + x 5 /5! – x 7 /7! (A very good approx) template < int degrees> const float 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);
    Floats can’t be declared inside the template class 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 floor any in-between degree values to index into our table
  • 31. Final Result: FastSine
    • // Approximates sine by degree float FastSine( float radians) { // Convert to degrees and floor result unsigned degrees = unsigned (radians * 180.0f/PI); // Wrap degrees and index SineTable return SineTable[degrees % 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
    • struct NullType {}; // Empty structure template < typename T, typename U = NullType> struct Tuple { typedef T head; typedef U tail;
    • T data; U next; };
  • 34. Making a Tuple
    • typedef Tuple< int , Tuple< float , Tuple<MyClass>>> MyType; MyType t; t.data // Element 1 t.next.data // Element 2 t.next.next.data // Element 3
    This is what I mean by “recursively defined”
  • 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<MyClass> Tuple<float, Tuple<MyClass>> Tuple<int, Tuple<float, Tuple<MyClass>>> data: int data: float data: MyClass NullType next next next
  • 37. Better creation
    • template < typename T1 = NullType, typename T2 = NullType, …> struct MakeTuple; template < typename T1> struct MakeTuple<T1, NullType, …> // Tuple of one type { typedef Tuple<T1> type; }; template < typename T1, typename T2> struct MakeTuple<T1, T2, …> // Tuple of two types { typedef Tuple<T1, Tuple<T2>> type; }; // Etc…
    Not the best solution, but simplifies syntax
  • 38. Making a Tuple Pt 2
    • typedef MakeTuple< int , float , MyClass> MyType; MyType t; t.data // Element 1 t.next.data // Element 2 t.next.next.data // Element 3
    But can we do something about this indexing mess? Better
  • 39. Better indexing
    • template < int index> struct GetValue { template < typename TList> static typename TList::head& From(TList& list) { return GetValue<index-1>::From(list.next); // Recurse } }; template <> struct GetValue<0> // Base case: Found the list data { template < typename TList> static typename TList::head& From(TList& list) { return list.data; } };
    It’s a good thing we made those typedefs Making use of template function auto-type detection again
  • 40. Better indexing
    • // Just to sugar up the syntax a bit #define TGet(list, index) GetValue<index>::From(list)
  • 41. Delicious Tuple
    • MakeTuple< int , float , MyClass> t; // TGet works for both access and mutation TGet(t, 0) // Element 1 TGet(t, 1) // Element 2 TGet(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: struct MyType { typedef int type; }; // Overloaded template functions template < typename T> void fnc(T arg); template < typename T> void fnc( typename T::type arg); void main() { fnc<MyType>(0); // Calls the second fnc fnc< 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 reject ill-formed function signatures
    • As we saw in the previous slide, int was ill-formed when matched with the function signature containing, typename T::type, but this did not cause an error
  • 45. Does MyClass have an iterator?
    • // Define types of different sizes typedef long Yes; typedef short No; template < typename T> Yes fnc( typename T::iterator*); // Must be pointer! template < typename T> No fnc(…); // Lowest priority signature void main() { // Sizeof check, can observe types without calling fnc printf( “Does MyClass have an iterator? %s ” , 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&)> struct SFINAE_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?