0% found this document useful (0 votes)
882 views3 pages

C++ Templates Are Turing Complete

This document describes how C++ templates can be used to simulate a Turing machine, thereby proving that C++ templates are Turing complete. It shows how to encode the states, tape, and transition function of a Turing machine as C++ template types and metafunctions. A configuration of the Turing machine is represented as a C++ class template. Evaluating the transition function and moving/updating the tape is done by instantiating helper classes, demonstrating that a Turing machine can be fully simulated using only C++ templates.

Uploaded by

hackermaw
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
882 views3 pages

C++ Templates Are Turing Complete

This document describes how C++ templates can be used to simulate a Turing machine, thereby proving that C++ templates are Turing complete. It shows how to encode the states, tape, and transition function of a Turing machine as C++ template types and metafunctions. A configuration of the Turing machine is represented as a C++ class template. Evaluating the transition function and moving/updating the tape is done by instantiating helper classes, demonstrating that a Turing machine can be fully simulated using only C++ templates.

Uploaded by

hackermaw
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 3

C++ Templates are Turing Complete

Todd L. Veldhuizen
[email protected]
Indiana University Computer Science

Abstract To encode the tape, we use a standard functional-style list:

We sketch a proof of a well-known folk theorem that C++ /* Tape representation */


templates are Turing complete. The absence of a formal struct Nil { };
semantics for C++ template instantiation makes a rigorous template<class Head, class Tail>
struct Pair {
proof unlikely.
typedef Head head;
typedef Tail tail;
1 Introduction };

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, ####).

template<int Depth, int A, typename B>


3 C++ Templates are Turing Complete struct K17 {
static const int x =
In the previous section, we gave an encoding of a simple K17<Depth+1, 0, K17<Depth,A,B> >::x
Turing machine in C++ templates. It is straightforward + K17<Depth+1, 1, K17<Depth,A,B> >::x
to encode any Turing machine in such a manner, by defin- + K17<Depth+1, 2, K17<Depth,A,B> >::x
ing appropriate alphabet and state types, and defining the + K17<Depth+1, 3, K17<Depth,A,B> >::x
relevant specializations of a transition function. + K17<Depth+1, 4, K17<Depth,A,B> >::x;
Let M be a Turing machine and α a starting configu- };
ration. Suppose α `M s1 `M s2 `M . . . is a trace of the template<int A, typename B>
struct K17<16,A,B> {
Turing machine. The following lemma states that if you
static const int x = 1;
compile a C++ program encoding M with an initial con- };
figuration corresponding to α, then the C++ compiler will static const int z = K17<0,0,int>::x;
produce instantiations of the Configuration template cor-
responding to s1 , s2 , . . .. An important qualification is that Figure 2: A standard-conforming C++ program which does
we assume a C++ compiler without limits on the number not exceed the limit of 17 recursively nested template instan-
of template instantiations it will produce. tiations, but nevertheless instantiates 517 =762,939,453,125
templates.
Lemma 1. Let M be a Turing machine, and α a start-
ing configuration. Let p be a C++ program encoding the
machine M and configuration α as outlined in the previous
This limit does not translate into any reliable time or
section. Let φ : (K × Σ∗ × Σ × Σ∗ ) → type be a map that en-
space bound on compiles, though; it is straightforward to
codes configurations of M as instances of the template type
construct C++ programs which instantiate k17 templates
Configuration. If α `∗M β, then a C++ compiler without
(i.e. within the recommended limit) for any arbitrarily large
instantiation limits will, in compiling p, instantiate φ(β).
k; see Figure 2 for an example with k = 5.
A formal proof of Lemma 1 presents problems, since one
would have to define formally the semantics of C++ tem- References
plate instantiation, something that to our knowledge has
never been attempted. I believe its truth would be apparent [1] ANSI/ISO. Working Paper for Draft Proposed Interna-
to anyone familiar with C++ template instantiation willing tional Standard for Information Systems– Programming
to comb through the encoding of the previous section. Language C++. Washington DC, April 1995. Doc. No.
Theorem 1. In the absence of instantiation bounds, C++ ANSI X3J16/95-0087 ISO WG21/N0687.
templates are Turing-complete. [2] Lewis, H. R., and Papadimitriou, C. H. Elements
of the theory of computation. Prentice-Hall, Englewood
Proof. Immediate from the construction of the previous sec-
Cliffs, New Jersey, 1981.
tion and Lemma 1.
[3] Unruh, E. Prime number computation, 1994. ANSI
A universal Turing machine is a special case of a Turing ma-
X3J16-94-0075/ISO WG21-462.
chine; thus UTMs can be implemented by C++ templates.
The usual diagonalization argument for undecidability ap-
plies. Therefore:
Corollary 1. In the absence of instantiation limits, whether
a C++ compiler will halt when compiling a given program
is undecidable.
In recognition of this difficulty, the C++ standards com-
mittee allows conforming compilers to limit the depth of
“recursively nested template instantiations,” with a recom-
mended minimum limit of 17 [1]. Compilers have adopted
this limit, many with an option to increase it.

You might also like