C++ Templates Are Turing Complete
C++ Templates Are Turing Complete
Todd L. Veldhuizen
[email protected]
Indiana University Computer Science
It has been known for some time that C++ templates permit Using these classes, the tape a#a is encoded as the C++
complicated computations to be performed at compile time. type Pair<A,Pair<Blank,Pair<A,Nil> > >. To represent
The first example was due to Erwin Unruh [3] who circu- the position of the Turing machine at a particular place on
lated a small C++ program that computed prime numbers the tape, we split the tape into three parts: to the left, the
at compile time, and listed them encoded as compiler error contents of the current tape cell, and to the right. So the
messages. In this short note we sketch a proof that C++ tape abcde, in which the Turing machine is positioned at
templates are Turing complete. We assume familiarity with d, would be represented as the triple (abc, d, e). To provide
both C++ templates and basic theory of computation; for easy access to the tape cell directly to the left of the read
background on Turing machines readers are referred to e.g. head, the left tape contents are stored in reverse order. So
[2]. The proof is straightforward: we show how any Turing the tape abcde would be encoded as these three types:
machine may be embedded in the C++ template instantia- abc Pair<C,Pair<B,Pair<A,Nil> > >
tion mechanism, from which the result is immediate. d D
e Pair<E,Nil>
2 Encoding Turing machines in C++ Templates The transition function δ(q, σ) maps from the current state
q and contents of the tape cell σ to the succeeding state
A Turing machine is a quadruple (K, Σ, δ, s), where K is a and action (character to be written, ⇐ or ⇒). To real-
finite set of states, Σ is an alphabet, s ∈ K is the start state, ize δ in templates, we provide specializations of a template
and δ is the transition function K ×Σ → (K ∪{h})×(Σ∪{⇐ class TransitionFunction<State,Character>. Inside each
, ⇒}). The special state h is the halt state, ⇐ and ⇒ are instance are typedefs for next state and action, which
special symbols indicating left and right, and # ∈ Σ is the encode (respectively) the next state and action:
blank symbol. /* Transition Function */
To illustrate how a Turing machine may be encoded as template<typename State, typename Character>
a C++ template metaprogram, we use as an example this struct TransitionFunction { };
simple machine which replaces a string of a’s with #’s and /* q0 a -> (q1,#) */
then halts: template<> struct TransitionFunction<Q0,A> {
typedef Q1 next_state;
K = {q0 , q1 , h} q σ δ(q, σ) typedef Blank action;
q0 a (q1 , #) };
Σ = {a, #} q0 # (h, #) /* q0 # -> (h,#) */
s = q0 q1 a (q0 , a) template<> struct TransitionFunction<Q0,Blank> {
typedef Halt next_state;
q1 # (q0 , ⇒)
typedef Blank action;
};
We encode the states K and alphabet Σ ∪ {⇐, ⇒} as empty /* q1 a -> (q0,a) */
C++ types: template<> struct TransitionFunction<Q1,A> {
/* Alphabet */ typedef Q0 next_state;
/* States */ typedef A action;
struct Left {};
struct Halt {}; };
struct Right {};
struct Q0 {}; /* q1 # -> (q0,->) */
struct A {};
struct Q1 {}; template<> struct TransitionFunction<Q1,Blank> {
struct Blank {};
typedef Q0 next_state;
1
typedef Right action; template<typename Q, typename Sigma> class Delta>
}; struct ApplyAction<NextState, Left, Tape_Left,
Tape_Current, Tape_Right, Delta>
A configuration is a member of K × Σ∗ × Σ × Σ∗ and repre- {
sents the state of the machine and tape at a single point in typedef Configuration<NextState,
the computation. We encode a configuration as an instance typename Tape_Left::tail,
typename Tape_Left::head,
of the template class Configuration<>, which takes these
Pair<Tape_Current,Tape_Right>,
template parameters: Delta>::halted_configuration
Template parameter Meaning halted_configuration;
State Current state of the machine };
Tape Left Contents of the tape to the left of
the read head (in reverse order) /* Move read head right */
Tape Current Content of the tape cell under template<typename NextState, typename Tape_Left,
the read head typename Tape_Current, typename Tape_Right,
Tape Right Contents of the tape to the template<typename Q, typename Sigma> class Delta>
struct ApplyAction<NextState, Right, Tape_Left,
right of the read head
Tape_Current, Tape_Right, Delta>
Delta Transition function {
Inside the class Configuration<>, the next state and typedef Configuration<NextState,
action are computed by evaluating δ(q, σ), and a helper Pair<Tape_Current,Tape_Left>,
class ApplyAction is instantiated to compute the next con- typename Tape_Right::head,
figuration: typename Tape_Right::tail,
Delta>::halted_configuration
/* Representation of a Configuration */ halted_configuration;
template<typename State, };
typename Tape_Left, /*
typename Tape_Current, * Move read head right when there are no nonblank characters
typename Tape_Right, * to the right -- generate a new Blank symbol.
template<typename Q, typename Sigma> class Delta> */
struct Configuration { template<typename NextState, typename Tape_Left,
typedef typename Delta<State,Tape_Current>::next_state typename Tape_Current,
next_state; template<typename Q, typename Sigma> class Delta>
typedef typename Delta<State,Tape_Current>::action struct ApplyAction<NextState, Right, Tape_Left,
action; Tape_Current, Nil, Delta>
typedef typename ApplyAction<next_state, action, {
Tape_Left, Tape_Current, Tape_Right, typedef Configuration<NextState,
Delta>::halted_configuration Pair<Tape_Current,Tape_Left>,
halted_configuration; Blank, Nil, Delta>::halted_configuration
}; halted_configuration;
};
The class ApplyAction has five versions, to handle:
template<typename Action, typename Tape_Left,
• Writing a character to the current tape cell; typename Tape_Current, typename Tape_Right,
template<typename Q, typename Sigma> class Delta>
• Transitioning to the halt state; struct ApplyAction<Halt, Action, Tape_Left,
Tape_Current, Tape_Right, Delta>
• Moving left; {
/*
• Moving right; * We halt by not declaring a halted_configuration.
* This causes the compiler to display an error message
• Moving right when at the rightmost non-blank cell on * showing the halting configuration.
the tape. */
};
Each of these instantiates the next Configuration<>, and
recursively defines the halted configuration. To “run” the Turing machine, we instantiate
Configuration<> on an appropriate starting configu-
/* Default action: write to current tape cell */
ration. For example, to apply the machine to the string
template<typename NextState, typename Action,
typename Tape_Left, typename Tape_Current, aaa, we use the starting configuration (q0 , aaa):
typename Tape_Right,
template<typename Q, typename Sigma> class Delta> /*
struct ApplyAction { * An example "run": on the tape aaa starting in state q0
typedef Configuration<NextState, Tape_Left, */
Action, Tape_Right, Delta>::halted_configuration typedef Configuration<Q0, Nil, A, Pair<A,Pair<A,Nil> >,
halted_configuration; TransitionFunction>::halted_configuration Foo;
};
When compiled with g++, this generates the error messages
/* Move read head left */ shown in Figure 1; the errors show a trace of the machine
template<typename NextState, from its starting configuration (q0 , aaa) to its halting con-
typename Tape_Left, typename Tape_Current, figuration (h, ####).
typename Tape_Right,
2
turing.cpp: In instantiation of ‘Configuration<Q0,Pair<Blank,Pair<Blank,Pair<Blank,Nil> > >,Blank,Nil,
TransitionFunction>’:
turing.cpp:82: instantiated from ‘Configuration<Q1,Pair<Blank,Pair<Blank,Nil> >,Blank,Nil,TransitionFunction>’
turing.cpp:82: instantiated from ‘Configuration<Q0,Pair<Blank,Pair<Blank,Nil> >,A,Nil,TransitionFunction>’
turing.cpp:82: instantiated from ‘Configuration<Q1,Pair<Blank,Nil>,Blank,Pair<A,Nil>,TransitionFunction>’
turing.cpp:82: instantiated from ‘Configuration<Q0,Pair<Blank,Nil>,A,Pair<A,Nil>,TransitionFunction>’
turing.cpp:82: instantiated from ‘Configuration<Q1,Nil,Blank,Pair<A,Pair<A,Nil> >,TransitionFunction>’
turing.cpp:82: instantiated from ‘Configuration<Q0,Nil,A,Pair<A,Pair<A,Nil>>,TransitionFunction>’
turing.cpp:163: instantiated from here
turing.cpp:91: no type named ‘halted_configuration’ in ‘struct ApplyAction<Halt,Blank,Pair<Blank,
Pair<Blank,Pair<Blank,Nil> > >,Blank,Nil,TransitionFunction>’
Figure 1: Compiler errors from g++ 2.95.2. Reading the error messages backwards, one sees the configuration trace (q0 , aaa)
`M (q1 , #aa) `M (q0 , #aa) `M (q1 , ##a) `M (q0 , ##a) `M (q1 , ###) `M (q0 , ####) `M (h, ####).