20
C++ Templates are Turing complete Mittagsseminar, Yves Brise, 20091103

C++ Templates are Turing complete - ETH Zurich · with C++ types. Transition Function template class Transition {}; // q0 a -> (q1,#) template

  • Upload
    lenhu

  • View
    216

  • Download
    0

Embed Size (px)

Citation preview

C++ Templates areTuring complete

Mittagsseminar, Yves Brise, 20091103

Discovery by Unruh, 1994template <int i> struct D { D(void*); };template <int p, int i> struct is_prime {  enum { prim = (p==2) || (p%i) && is_prime<(i>2?p:0), i-1>::prim }; };template<> struct is_prime<0,0> { enum {prim=1}; };template<> struct is_prime<0,1> { enum {prim=1}; };template <int i> struct Prime_print {  Prime_print<i-1> a;  enum { prim = is_prime<i, i-1>::prim };  void f() { D<i> d = prim ? 1 : 0; a.f();} };template<> struct Prime_print<1> {  enum { prim = 0 };  void f() { D<1> d = prim ? 1 : 0; }; };

int main() {  Prime_print<18> a;  a.f(); return 0; }

Templates and Metaprogramming

Turing Completeness of TMP

Application: Sparse Vectors

Outline

C++ Templatestemplate <class T> class myClass {T variable_of_type_T;...

};

template <class T> void myFunction(T& t) {...

}

Parametrizationby unknown type.Compiler generatescode for each typerequested.

Introduced in the late 80’s early 90’sGeneric algorithms, e.g., sorting. Only assume operator< to be defined.Containers, e.g., vectors, lists, maps, etc...C++ Standard Template Library

Template Specializationtemplate <class T> class myVector {T* array_of_T;...

};

template<> class myVector<bool> {int storage; // stores 32 bools...

};

Used to definespecial behaviorfor certain cases.

More efficient implementation without changing the interface of the template.Compiler chooses most specialized variant.In TMP specialization is essential to implement control structures (if, while).

TMP

Compile-time vs. execution-time tradeoffcomputation of constantsloop unrollingavoiding function calls and temporary variables

Readability, Debugging

A metaprogram is a program than manipulates code.In C++ compilation there are two phases:

1. Compiler instantiates templates2. Result of phase 1 is compiled the old fashioned way

Turing completenessTheorem (Veldhuizen, 2003): In the absence ofinstantiation bounds, C++ templates are Turingcomplete.

A Turing machine is a 4-tuple (Q, Σ, δ, q0),where Q is a set of states, Σ is the tape alphabet,δ is the transition function, and q0 ∈ Q is thestarting state.

δ : Q × Σ→ Q × Σ ∪ {⇒,⇐}

h ∈ Q (halting state), # ∈ Σ (blank symbol).

We represent the tape as Σ∗ × Σ× Σ∗

Proof by Example

Σ = {a, #}s = q0

q σ δ(q,σ)q0 a (q1, #)q0 # (h, #)q1 a (q0, a)q1 # (q0,⇒)

q0 q1h

a/a

#/⇒

#/#

a/#

... # a a a # # ...Q = {q0, q1, h}

Task: Replace a’s by #’s

Tape & States

// Tape representationclass Nil {};template <class Head, class Tail>class Pair {typedef Head head;typedef Tail tail;

}

// Statesclass Halt {};class Q0 {};class Q1 {};

// Alphabetclass Left {};class Right {};class A {};class Blank {};

We will encode the tape and the current state as a triplet Left, Current, and Right, i.e. ...#aaa#... corresponds to the three typesNil, A, and Pair<A,Pair<A,Nil> >.

Main idea: Functional programming with C++ types.

Transition Functiontemplate <class State, class Character>class Transition {};

// q0 a -> (q1,#)template<> class Transition<Q0,A> {typedef Q1 next_state;typedef Blank action;

}

Similarly for δ(q0, #), δ(q1, a), and δ(q1, #).

We define template specializations for every state andcurrent symbol. The specializations contain typedefsfor the next state and the symbol to write (or action).

Configurationtemplate <class State, class Tape_Left, class Tape_Current, class Tape_Right, template<class Q, class Sigma> class Delta>class Configuration { typedef typename Delta<State, Tape_Current>::next_state

next_state; typedef typename Delta<State, Tape_Current>::action

action; typedef typename ApplyAction<next_state, action, Tape_Left, Tape_Current, Tape_Right, Delta>::halted_config

halted_config;};

Forcing an Errortemplate <class NextState,...>class ApplyAction { typedef typename Configuration<NextState,...>::halted_config

halted_config;};

ApplyAction is a helper template that defers thetype halted_config to the next Configurationdown the road.There are 5 different specializations. One of them makes the transition to the halting state.

template <...>class ApplyAction<Halt,...> { // no typedef! };

Summary

// example runtypedef Configuration<Q0, Nil, A, Pair<A,Pair<A,Nil> >, Transition>::halted_configFoo;

Everything is represented as C++ types. In order to “run” our Turing machine, we do the following...

Two obstacles in ISO 14882 (C++ standard):Finite instantiation bound (at least 17)Template semantics is formulated using natural language. “A Semantic Analysis of C++ Templates”, Siek, Taha, 2006

Sparse VectorsSuppose you have two vectors x = (0, 0, 1, 0, 1)T andy = (2, 0, 2, 0, 0)T , and you want to add them (c = x + y).

A dense implementation uses 5 additions.

A sparse implementation uses 3 additions, and some indexing overhead.

We’re going to see an implementation than uses 1 addition, 2 copy operations, and no indexing overhead.

Representing Entries

template<unsigned int n> class Elem {};

template<> class Elem<1> { public: double value; ...};

We use a templateclass to represent entries in the vector.

Only Elem<1> contains a data member, and only Elem<0> and Elem<1> should be used.

Representing Vectors

template<unsigned int n> class CTSV { public: Elem<n&1> head; CTSV<n>>1> tail;

...};

template<> class CTSV<0> { public: Elem<0> head; ...};

The integer n encodes the sparsity pattern.

We specialize CTSV<0> as the base case.

Operation: Addingtemplate <unsigned int n, unsigned int m>static inline voidadd (const CTSV<n>& a, const CTSV<m>& b, CTSV<n|m>& c) {add_elem(a.head, b.head, c.head);

plus< n>>1, m>>1 >::add(a.getTail(), b.getTail(),c.getTail());

}

The function add can be used to overload operator+ for the class CTSV.It contains one call to the function add_elem and one recursive call to itself.

Adding Elementsvoid add_elem(const Elem<0>& a, const Elem<0>& b, Elem<0>& c) {}

void add_elem(const Elem<0>& a, const Elem<1>& b, Elem<1>& c) {c.value = b.value;}

void add_elem(const Elem<1>& a, const Elem<0>& b, Elem<1>& c) {c.value = a.value;}

void add_elem(const Elem<1>& a, const Elem<1>& b, Elem<1>& c) {c.value = a.value + b.value;}

Only in the last case an actual addition will be performed. In practice, we’ll make these inline.

Using itCTSV<5> x(1);CTSV<20> y(2);x+y;

x = (0, 0, 1, 0, 1)T , because 510 = 001012

y = (2, 0, 2, 0, 0)T , because 2010 = 101002

mov dword ptr [ebp-12],1mov dword ptr [ebp-7],1mov dword ptr [ebp-174],2mov dword ptr [ebp-169],2mov eax,dword ptr [ebp-12]mov dword ptr [ebp-192],eaxmov edx,dword ptr [ebp-7]add edx,dword ptr [ebp-174]mov dword ptr[ebp-187],edxmov ecx,dword ptr [ebp-169]mov dword ptr [ebp-182],ecx

The product is highly optimized assembler code. This example is from Jaakko Järvi’s paper, 1997.Borland C++ 5.01 on Intel 80486.

#include <iostream>

int main() {std::cout << “Thank you for/

your attention.\n”;return 0;

}