practical meta-programming

50
Practical Meta- programming By Reggie Meisler

Upload: elvina

Post on 21-Jan-2016

46 views

Category:

Documents


1 download

DESCRIPTION

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. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: Practical Meta-programming

Practical Meta-programming

By Reggie Meisler

Page 2: Practical Meta-programming

Topics

Page 3: Practical Meta-programming

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

Page 4: Practical Meta-programming

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

Page 5: Practical Meta-programming

Template mechanics

template <typename T>class MyClass { /*…*/ };

// Full specializationtemplate <>class MyClass<int> { /*…*/ };

// Partial specializationtemplate <typename T>class MyClass<T*> { /*…*/ };

Page 6: Practical Meta-programming

Template mechanics

template <typename T>class MyClass { /*…*/ };

// Full specializationtemplate <>class MyClass<int> { /*…*/ };

// Partial specializationtemplate <typename T>class MyClass<T*> { /*…*/ };

MyClass<float> goes here

MyClass<int> goes here

MyClass<int*> goes here

Page 7: Practical Meta-programming

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

Page 8: Practical Meta-programming

How it works in general// Example of a simple summationtemplate <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

Page 9: Practical Meta-programming

How it works in general

// Example of a simple summationint mySum = Sum<10>::value;

// mySum = 55 = 10 + 9 + 8 + … + 3 + 2 + 1

Page 10: Practical Meta-programming

How it works in general// Example of a type trait that checks for consttemplate <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;};

Page 11: Practical Meta-programming

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

Page 12: Practical Meta-programming

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

Page 13: Practical Meta-programming

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

Page 14: Practical Meta-programming

Fibonaccitemplate <int N> // 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;};

Page 15: Practical Meta-programming

Fibonacci

• Now let’s use it!

// Print out 42 fib valuesfor( int i = 0; i < 42; ++i ) printf(“fib(%d) = %d\n”, i, Fib<i>::value);

• What’s wrong with this picture?

Page 16: Practical Meta-programming

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!

Page 17: Practical Meta-programming

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)

Page 18: Practical Meta-programming

Fibonacci Tableint 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() {}};

Page 19: Practical Meta-programming

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

Page 20: Practical Meta-programming

Using Fibonacci

// Print out 42 fib valuesfor( int i = 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// …

Page 21: Practical Meta-programming

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?

Page 22: Practical Meta-programming

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

Page 23: Practical Meta-programming

Dot Producttemplate <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 prodtemplate <typename T>struct DotProd<T, 1>{ static T Do(const T* a, const T* b) { return (*a) * (*b); }};

Page 24: Practical Meta-programming

Dot Product

// 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

Always take advantage of

auto-type detection!

Page 25: Practical Meta-programming

Dot Product

// Other possible method, assuming POD vector// * Probably more practicaltemplate <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);}

Page 26: Practical Meta-programming

Dot Product

// Other possible method, assuming POD vector// * Probably more practicaltemplate <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

Page 27: Practical Meta-programming

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)

Page 28: Practical Meta-programming

Approximating Sinetemplate <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>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);

Page 29: Practical Meta-programming

Approximating Sinetemplate <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>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

Page 30: Practical Meta-programming

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

Page 31: Practical Meta-programming

Final Result: FastSine

// Approximates sine, favors ceil valuefloat FastSine(float radians){ // Convert to degrees float 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 SineTable return t * SineTable[approxB % 360] + (1-t) * SineTable[approxA % 360];}

Page 32: Practical Meta-programming

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

Page 33: Practical Meta-programming

Tuples

struct NullType {}; // Empty structure

template <typename T, typename U = NullType>struct Tuple{ typedef T head; typedef U tail; T data; U next;};

Page 34: Practical Meta-programming

Making a Tuple

typedef Tuple<int, Tuple<float, Tuple<MyClass>>> MyType;

MyType t;

t.data // Element 1t.next.data // Element 2t.next.next.data // Element 3

This is what I mean by “recursively defined”

Page 35: Practical Meta-programming

Tuple<int, Tuple<float, Tuple<MyClass>>>

Tuple in memory

data: int

next: Tuple<float, Tuple<MyClass>>

data: float

next: Tuple<MyClass>

data: MyClass

next: NullType

Page 36: Practical Meta-programming

Tuple<MyClass>

Tuple<float, Tuple<MyClass>>

Tuple<int, Tuple<float, Tuple<MyClass>>>

data: int

data: float

data: MyClass

NullType

next

next

next

Page 37: Practical Meta-programming

Better creationtemplate <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

Page 38: Practical Meta-programming

Making a Tuple Pt 2

typedef MakeTuple<int, float, MyClass> MyType;

MyType t;

t.data // Element 1t.next.data // Element 2t.next.next.data // Element 3

But can we do something about this

indexing mess?

Better

Page 39: Practical Meta-programming

Better indexingtemplate <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

Page 40: Practical Meta-programming

Better indexing

// Just to sugar up the syntax a bit#define TGet(list, index) \

GetValue<index>::From(list)

Page 41: Practical Meta-programming

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

Page 42: Practical Meta-programming

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

Page 43: Practical Meta-programming

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 functionstemplate <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)}

Page 44: Practical Meta-programming

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, int was ill-formed when matched with the function signature containing, typename T::type, but this did not cause an error

Page 45: Practical Meta-programming

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 \n”, sizeof(fnc<MyClass>(0)) == sizeof(Yes) ? “Yes” : “No”);}

Page 46: Practical Meta-programming

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

Page 47: Practical Meta-programming

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

Page 48: Practical Meta-programming

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(…);

Page 49: Practical Meta-programming

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

Page 50: Practical Meta-programming

Questions?