44

We know that C++ template metaprogramming is Turing complete, but preprocessor metaprogramming is not.

C++11 gives us a new form of metaprogramming: computation of constexpr functions. Is this form of computation Turing-complete? I am thinking that since recursion and the conditional operator (?:) are allowed in constexpr functions, it would be, but I would like someone with more expertise to confirm.

Community
  • 1
  • 1
HighCommander4
  • 44,537
  • 22
  • 112
  • 180

3 Answers3

60

tl;dr: constexpr in C++11 was not Turing-complete, due to a bug in the specification of the language, but that bug has been addressed in later drafts of the standard, and clang already implements the fix.

constexpr, as specified in the ISO C++11 international standard, is not Turing-complete. Sketch proof:

  • Every constexpr function f's result (or non-termination) on a particular sequence of arguments, a..., is determined only by the values of a...
  • Every argument value that can be constructed inside a constant expression must be of a literal type, which by [basic.types]p10 is either:
    • a scalar type,
    • a reference,
    • an array of literal type, or
    • a class type
  • Each of the above cases has a finite set of values.
    • For a scalar, non-pointer type, this follows trivially.
    • For a pointer or reference to be used in a constant expression, it must be initialized by an address or reference constant expression, so must refer to an object with static storage duration, of which there are only a finite quantity in any program.
    • For an array, the bound must be a constant, and each member must be initialized by a constant expression, from which the result follows.
    • For a class type, there are a finite number of members, and each member must be of literal type and initialized by a constant expression, from which the result follows.
  • Therefore, the set of possible inputs a... which f can receive is finite, so any finitely-described constexpr system is a finite state machine, and thus is not Turing-complete.

However, since the publication of the C++11 standard, the situation has changed.

The problem described in Johannes Schaub's answer to std::max() and std::min() not constexpr was reported to the C++ standardization committee as core issue 1454. At the February 2012 WG21 meeting, we decided that this was a defect in the standard, and the chosen resolution included the ability to create values of class types with pointer or reference members that designate temporaries. This allows an unbounded quantity of information to be accumulated and processed by a constexpr function, and is sufficient to make constexpr evaluation Turing-complete (assuming that the implementation supports recursion to an unbounded depth).

In order to demonstrate the Turing-completeness of constexpr for a compiler that implements the proposed resolution of core issue 1454, I wrote a Turing-machine simulator for clang's test suite:

http://llvm.org/svn/llvm-project/cfe/trunk/test/SemaCXX/constexpr-turing.cpp

Trunk versions of both g++ and clang implement the resolution of this core issue, but g++'s implementation currently is unable to process this code.

Community
  • 1
  • 1
Richard Smith
  • 12,637
  • 52
  • 71
  • 1
    Interesting! If I understood correctly, the distinction hinges on the fact that a program can only have a finite number of objects of static storage duration, but it can have a potentially infinite number of temporaries. Could you explain why that is? – HighCommander4 Mar 02 '12 at 08:34
  • 3
    @HighCommander4 Each object of static storage duration is introduced by a declaration in the source code (of which there are only a finite number, and each introduces only a finite number of separately-addressable objects), whereas unbounded recursion can introduce an unbounded number of temporaries. This point of view applies only to the C++ abstract machine -- every real implementation will eventually hit some form of resource limit, so still has some finite (but typically unknown) bound. – Richard Smith Mar 05 '12 at 07:57
  • 2
    How very Abstract :-) – Kerrek SB Sep 28 '13 at 23:22
  • @RichardSmith: Don't you need to deal with template parameter-packs somehow? I mean, the "set of possible inputs `a...` which `f` can receive" is to me *clearly* infinite: there's `f(1)`, and `f(1,1)`, and `f(1,1,1)`, and so on to (countable) infinity. I'm not saying C++11 constexpr is necessarily Turing-complete, I'm just saying you haven't really got a proof to the contrary because you're starting from flawed premises. (Unless your missing premise is the implementation-defined maximum size of a parameter pack.) – Quuxplusone May 13 '14 at 02:58
  • 2
    @Quuxplusone No, constant expression evaluation can't trigger template instantiation, so templates aren't relevant here. Constant expression evaluation only operates on non-(value-)dependent fragments of the program. (Interleaved template instantiation and constant expression evaluation is Turing-complete, but that follows from template instantiation being Turing-complete by itself.) – Richard Smith May 13 '14 at 18:57
8

Have a look at these. I compiled the examples and they work in GCC 4.6: Compile-time computations, Parsing strings at compile-time - Part I, Parsing strings at compile-time - Part II

Andrzej
  • 4,435
  • 23
  • 32
  • 1
    +1 : OMG now I see what new madness/awesomeness made possible by constexpr they were talking about in GoingNative conference panel O__O; – Klaim Feb 09 '12 at 01:49
  • String parsing is where translationtime computing gets beautiful. – ex0du5 Feb 09 '12 at 21:05
  • 7
    Being able to read a string literal doesn't mean it's Turing complete (e.g. it doesn't demonstrate how to write on an infinite (not semi-infinite) tape). – kennytm Feb 10 '12 at 12:50
1

If we take in account restrictions of real computer - such as finite memory and finite value of MAX_INT - then, of course, constexpr (and also the whole C++) is not Turing-complete.

But if we will remove this restriction - for example, if we will think about int as a completely arbitary positive integer - then yes, constexpr part of C++ will be Turing complete. It is easy to express any partial recursive function.

0, S(n) = n+1 and selectors I_n^m(x_1, ..., x_n) = x_m and superposition obviously can be expressed using constexpr.

Primitive recursion can be done it straight way:

constexpr int h(int x1, ..., int xn, int y) {
  return (xn == 0) ? f(x1, ..., xn) : g(x1, ..., xn, y-1, h(x1, ..., xn, y-1));
}

And for partial recursion we need a simple trick:

constexpr int h(int x1, ... int xn, int y = 0) {
  return (f(x1, ... xn, y) == 0) ? y : h(x1, ..., xn, y+1);
}

So we get any partial recursion function as a constexpr.

mihaild
  • 248
  • 1
  • 9
  • For an `int` type that models mathematical integers, this answer is correct. However, for an `int` type with only a finite set of distinct values, the result does not follow. – Richard Smith Feb 24 '20 at 18:34