Les modèles C++ Turing-complet?

on m'a dit que le système de template en C++ est Turing-complete au moment de la compilation. Ceci est mentionné dans ce post et aussi sur wikipedia .

Pouvez-vous fournir un exemple non trivial d'un calcul qui exploite cette propriété?

est-ce utile dans la pratique?

90
demandé sur Community 2008-10-10 00:53:46

15 réponses

exemple

#include <iostream>

template <int N> struct Factorial
{
    enum { val = Factorial<N-1>::val * N };
};

template<>
struct Factorial<0>
{
    enum { val = 1 };
};

int main()
{
    // Note this value is generated at compile time.
    // Also note that most compilers have a limit on the depth of the recursion available.
    std::cout << Factorial<4>::val << "\n";
}

C'était un peu amusant, mais pas très pratique.

pour répondre à la deuxième partie de la question:

ce fait est-il utile dans la pratique?

réponse courte: en quelque sorte.

longue Réponse: Oui, mais seulement si vous êtes un démon modèle.

pour obtenir une bonne programmation en utilisant le modèle la méta-programmation qui est vraiment utile pour les autres (c'est-à-dire une bibliothèque) est vraiment difficile (mais faisable). Pour aider boost a même MPL aka (Meta Programming Library). Mais essayez de déboguer une erreur de compilateur dans votre code de template et vous en aurez pour longtemps.

mais un bon exemple pratique de son utilisation pour quelque chose d'utile:

Scott Meyers a travaillé sur des extensions du langage C++ (j'utilise le terme vaguement) en utilisant les installations de templating. Vous pouvez lire à propos de son travail ici ' Enforcing Code Features '

89
répondu Martin York 2008-12-13 19:27:13

j'ai fait une machine de turing en C++11. Les fonctionnalités ajoutées par C++11 ne sont pas significatives pour la machine de turing. Il fournit juste des listes de règles de longueur arbitraires en utilisant des modèles variadiques, au lieu d'utiliser la macro-métaprogrammation perverse :). Les noms des conditions sont utilisés pour afficher un diagramme sur stdout. j'ai enlevé ce code pour garder l'échantillon court.

#include <iostream>

template<bool C, typename A, typename B>
struct Conditional {
    typedef A type;
};

template<typename A, typename B>
struct Conditional<false, A, B> {
    typedef B type;
};

template<typename...>
struct ParameterPack;

template<bool C, typename = void>
struct EnableIf { };

template<typename Type>
struct EnableIf<true, Type> {
    typedef Type type;
};

template<typename T>
struct Identity {
    typedef T type;
};

// define a type list 
template<typename...>
struct TypeList;

template<typename T, typename... TT>
struct TypeList<T, TT...>  {
    typedef T type;
    typedef TypeList<TT...> tail;
};

template<>
struct TypeList<> {

};

template<typename List>
struct GetSize;

template<typename... Items>
struct GetSize<TypeList<Items...>> {
    enum { value = sizeof...(Items) };
};

template<typename... T>
struct ConcatList;

template<typename... First, typename... Second, typename... Tail>
struct ConcatList<TypeList<First...>, TypeList<Second...>, Tail...> {
    typedef typename ConcatList<TypeList<First..., Second...>, 
                                Tail...>::type type;
};

template<typename T>
struct ConcatList<T> {
    typedef T type;
};

template<typename NewItem, typename List>
struct AppendItem;

template<typename NewItem, typename...Items>
struct AppendItem<NewItem, TypeList<Items...>> {
    typedef TypeList<Items..., NewItem> type;
};

template<typename NewItem, typename List>
struct PrependItem;

template<typename NewItem, typename...Items>
struct PrependItem<NewItem, TypeList<Items...>> {
    typedef TypeList<NewItem, Items...> type;
};

template<typename List, int N, typename = void>
struct GetItem {
    static_assert(N > 0, "index cannot be negative");
    static_assert(GetSize<List>::value > 0, "index too high");
    typedef typename GetItem<typename List::tail, N-1>::type type;
};

template<typename List>
struct GetItem<List, 0> {
    static_assert(GetSize<List>::value > 0, "index too high");
    typedef typename List::type type;
};

template<typename List, template<typename, typename...> class Matcher, typename... Keys>
struct FindItem {
    static_assert(GetSize<List>::value > 0, "Could not match any item.");
    typedef typename List::type current_type;
    typedef typename Conditional<Matcher<current_type, Keys...>::value, 
                                 Identity<current_type>, // found!
                                 FindItem<typename List::tail, Matcher, Keys...>>
        ::type::type type;
};

template<typename List, int I, typename NewItem>
struct ReplaceItem {
    static_assert(I > 0, "index cannot be negative");
    static_assert(GetSize<List>::value > 0, "index too high");
    typedef typename PrependItem<typename List::type, 
                             typename ReplaceItem<typename List::tail, I-1,
                                                  NewItem>::type>
        ::type type;
};

template<typename NewItem, typename Type, typename... T>
struct ReplaceItem<TypeList<Type, T...>, 0, NewItem> {
    typedef TypeList<NewItem, T...> type;
};

enum Direction {
    Left = -1,
    Right = 1
};

template<typename OldState, typename Input, typename NewState, 
         typename Output, Direction Move>
struct Rule {
    typedef OldState old_state;
    typedef Input input;
    typedef NewState new_state;
    typedef Output output;
    static Direction const direction = Move;
};

template<typename A, typename B>
struct IsSame {
    enum { value = false }; 
};

template<typename A>
struct IsSame<A, A> {
    enum { value = true };
};

template<typename Input, typename State, int Position>
struct Configuration {
    typedef Input input;
    typedef State state;
    enum { position = Position };
};

template<int A, int B>
struct Max {
    enum { value = A > B ? A : B };
};

template<int n>
struct State {
    enum { value = n };
    static char const * name;
};

template<int n>
char const* State<n>::name = "unnamed";

struct QAccept {
    enum { value = -1 };
    static char const* name;
};

struct QReject {
    enum { value = -2 };
    static char const* name; 
};

#define DEF_STATE(ID, NAME) \
    typedef State<ID> NAME ; \
    NAME :: name = #NAME ;

template<int n>
struct Input {
    enum { value = n };
    static char const * name;

    template<int... I>
    struct Generate {
        typedef TypeList<Input<I>...> type;
    };
};

template<int n>
char const* Input<n>::name = "unnamed";

typedef Input<-1> InputBlank;

#define DEF_INPUT(ID, NAME) \
    typedef Input<ID> NAME ; \
    NAME :: name = #NAME ;

template<typename Config, typename Transitions, typename = void> 
struct Controller {
    typedef Config config;
    enum { position = config::position };

    typedef typename Conditional<
        static_cast<int>(GetSize<typename config::input>::value) 
            <= static_cast<int>(position),
        AppendItem<InputBlank, typename config::input>,
        Identity<typename config::input>>::type::type input;
    typedef typename config::state state;

    typedef typename GetItem<input, position>::type cell;

    template<typename Item, typename State, typename Cell>
    struct Matcher {
        typedef typename Item::old_state checking_state;
        typedef typename Item::input checking_input;
        enum { value = IsSame<State, checking_state>::value && 
                       IsSame<Cell,  checking_input>::value
        };
    };
    typedef typename FindItem<Transitions, Matcher, state, cell>::type rule;

    typedef typename ReplaceItem<input, position, typename rule::output>::type new_input;
    typedef typename rule::new_state new_state;
    typedef Configuration<new_input, 
                          new_state, 
                          Max<position + rule::direction, 0>::value> new_config;

    typedef Controller<new_config, Transitions> next_step;
    typedef typename next_step::end_config end_config;
    typedef typename next_step::end_input end_input;
    typedef typename next_step::end_state end_state;
    enum { end_position = next_step::position };
};

template<typename Input, typename State, int Position, typename Transitions>
struct Controller<Configuration<Input, State, Position>, Transitions, 
                  typename EnableIf<IsSame<State, QAccept>::value || 
                                    IsSame<State, QReject>::value>::type> {
    typedef Configuration<Input, State, Position> config;
    enum { position = config::position };
    typedef typename Conditional<
        static_cast<int>(GetSize<typename config::input>::value) 
            <= static_cast<int>(position),
        AppendItem<InputBlank, typename config::input>,
        Identity<typename config::input>>::type::type input;
    typedef typename config::state state;

    typedef config end_config;
    typedef input end_input;
    typedef state end_state;
    enum { end_position = position };
};

template<typename Input, typename Transitions, typename StartState>
struct TuringMachine {
    typedef Input input;
    typedef Transitions transitions;
    typedef StartState start_state;

    typedef Controller<Configuration<Input, StartState, 0>, Transitions> controller;
    typedef typename controller::end_config end_config;
    typedef typename controller::end_input end_input;
    typedef typename controller::end_state end_state;
    enum { end_position = controller::end_position };
};

#include <ostream>

template<>
char const* Input<-1>::name = "_";

char const* QAccept::name = "qaccept";
char const* QReject::name = "qreject";

int main() {
    DEF_INPUT(1, x);
    DEF_INPUT(2, x_mark);
    DEF_INPUT(3, split);

    DEF_STATE(0, start);
    DEF_STATE(1, find_blank);
    DEF_STATE(2, go_back);

    /* syntax:  State, Input, NewState, Output, Move */
    typedef TypeList< 
        Rule<start, x, find_blank, x_mark, Right>,
        Rule<find_blank, x, find_blank, x, Right>,
        Rule<find_blank, split, find_blank, split, Right>,
        Rule<find_blank, InputBlank, go_back, x, Left>,
        Rule<go_back, x, go_back, x, Left>,
        Rule<go_back, split, go_back, split, Left>,
        Rule<go_back, x_mark, start, x, Right>,
        Rule<start, split, QAccept, split, Left>> rules;

    /* syntax: initial input, rules, start state */
    typedef TuringMachine<TypeList<x, x, x, x, split>, rules, start> double_it;
    static_assert(IsSame<double_it::end_input, 
                         TypeList<x, x, x, x, split, x, x, x, x>>::value, 
                "Hmm... This is borky!");
}
154
répondu Johannes Schaub - litb 2015-01-26 17:09:39

les gabarits C++ sont Turing Complete " donne une implémentation d'une machine de Turing dans les gabarits ... ce qui n'est pas anodin et prouve le fait d'une manière très directe. Bien sûr, ce n'est pas très utile!

25
répondu Rob Walker 2016-12-01 21:45:17

mon C++ est un peu rouillé, donc ce n'est peut-être pas parfait, mais c'est proche.

template <int N> struct Factorial
{
    enum { val = Factorial<N-1>::val * N };
};

template <> struct Factorial<0>
{
    enum { val = 1 };
}

const int num = Factorial<10>::val;    // num set to 10! at compile time.

le but est de démontrer que le compilateur évalue complètement la définition récursive jusqu'à ce qu'il obtienne une réponse.

13
répondu James Curran 2009-08-28 15:31:58

pour donner un exemple non négligeable: http://gitorious.org/metatrace , A C++ compile time ray tracer.

noter que C++0x ajoutera un non-modèle, compiler-time, turing-complete facility sous forme de constexpr :

constexpr unsigned int fac (unsigned int u) {
        return (u<=1) ? (1) : (u*fac(u-1));
}

vous pouvez utiliser constexpr - expression partout où vous avez besoin compiler les constantes de temps, mais vous pouvez aussi appeler constexpr - fonctions avec des paramètres non-const.

Ce qui est cool, c'est que cela va finalement permettre de compiler les mathématiques à virgule flottante, bien que la norme stipule explicitement que les arithmétiques à virgule flottante à compiler le temps n'ont pas à correspondre aux arithmétiques à virgule flottante à l'exécution:

bool f(){
    char array[1+int(1+0.2-0.1-0.1)]; //Must be evaluated during translation
    int  size=1+int(1+0.2-0.1-0.1); //May be evaluated at runtime
    return sizeof(array)==size;
}

Il n'est pas précisé si la valeur de f() sera vrai ou faux.

9
répondu Sebastian Mach 2016-02-04 11:30:37

Le Livre Modern C++ Design - Programmation Générique et le Design Pattern par Andrei Alexandrescu est le meilleur endroit pour obtenir les mains sur l'expérience utile et puissant générique de programmation: modèles.

8
répondu yoav.aviram 2008-10-09 23:34:12

l'exemple factoriel ne montre en fait pas que les gabarits sont Turing complets, autant qu'il montre qu'ils supportent la récursion Primitive. La façon la plus facile de montrer que les gabarits sont turing complet est par la thèse de Turing D'Église, c'est-à-dire en mettant en œuvre soit une machine de Turing (désordre et un peu inutile) ou les trois règles (app, abs var) du calcul lambda non typé. Le dernier est beaucoup plus simple et beaucoup plus intéressant.

Ce qui est discuté est un fonction extrêmement utile lorsque vous comprenez que les modèles C++ permettent une programmation purement fonctionnelle au moment de la compilation, un formalisme qui est expressif, puissant et élégant, mais aussi très compliqué à écrire si vous avez peu d'expérience. Remarquez aussi combien de personnes trouvent que le simple fait d'obtenir du code fortement Templé peut souvent exiger un gros effort: c'est exactement le cas avec les langages fonctionnels (purs), qui rendent la compilation plus difficile mais donnent étonnamment du code qui ne nécessite pas de débogage.

7
répondu 2009-04-29 09:56:43

je pense que ça s'appelle modèle de méta-programmation .

5
répondu Tom Ritter 2008-10-09 21:01:29

vous pouvez consulter cet article de Dr.Dobbs sur une implémentation FFT avec des gabarits qui je ne pense pas que trivial. Le point principal est de permettre au compilateur d'effectuer une meilleure optimisation que pour les implémentations sans gabarit car l'algorithme FFT utilise beaucoup de constantes ( tables de sin par exemple)

partie I

partie II

3
répondu 2008-10-10 12:53:38

Eh bien, voici une compilation Time Turing machine implementation exécutant un castor à 4 états à 2 symboles occupé

#include <iostream>

#pragma mark - Tape

constexpr int Blank = -1;

template<int... xs>
class Tape {
public:
    using type = Tape<xs...>;
    constexpr static int length = sizeof...(xs);
};

#pragma mark - Print

template<class T>
void print(T);

template<>
void print(Tape<>) {
    std::cout << std::endl;
}

template<int x, int... xs>
void print(Tape<x, xs...>) {
    if (x == Blank) {
        std::cout << "_ ";
    } else {
        std::cout << x << " ";
    }
    print(Tape<xs...>());
}

#pragma mark - Concatenate

template<class, class>
class Concatenate;

template<int... xs, int... ys>
class Concatenate<Tape<xs...>, Tape<ys...>> {
public:
    using type = Tape<xs..., ys...>;
};

#pragma mark - Invert

template<class>
class Invert;

template<>
class Invert<Tape<>> {
public:
    using type = Tape<>;
};

template<int x, int... xs>
class Invert<Tape<x, xs...>> {
public:
    using type = typename Concatenate<
        typename Invert<Tape<xs...>>::type,
        Tape<x>
    >::type;
};

#pragma mark - Read

template<int, class>
class Read;

template<int n, int x, int... xs>
class Read<n, Tape<x, xs...>> {
public:
    using type = typename std::conditional<
        (n == 0),
        std::integral_constant<int, x>,
        Read<n - 1, Tape<xs...>>
    >::type::type;
};

#pragma mark - N first and N last

template<int, class>
class NLast;

template<int n, int x, int... xs>
class NLast<n, Tape<x, xs...>> {
public:
    using type = typename std::conditional<
        (n == sizeof...(xs)),
        Tape<xs...>,
        NLast<n, Tape<xs...>>
    >::type::type;
};

template<int, class>
class NFirst;

template<int n, int... xs>
class NFirst<n, Tape<xs...>> {
public:
    using type = typename Invert<
        typename NLast<
            n, typename Invert<Tape<xs...>>::type
        >::type
    >::type;
};

#pragma mark - Write

template<int, int, class>
class Write;

template<int pos, int x, int... xs>
class Write<pos, x, Tape<xs...>> {
public:
    using type = typename Concatenate<
        typename Concatenate<
            typename NFirst<pos, Tape<xs...>>::type,
            Tape<x>
        >::type,
        typename NLast<(sizeof...(xs) - pos - 1), Tape<xs...>>::type
    >::type;
};

#pragma mark - Move

template<int, class>
class Hold;

template<int pos, int... xs>
class Hold<pos, Tape<xs...>> {
public:
    constexpr static int position = pos;
    using tape = Tape<xs...>;
};

template<int, class>
class Left;

template<int pos, int... xs>
class Left<pos, Tape<xs...>> {
public:
    constexpr static int position = typename std::conditional<
        (pos > 0),
        std::integral_constant<int, pos - 1>,
        std::integral_constant<int, 0>
    >::type();

    using tape = typename std::conditional<
        (pos > 0),
        Tape<xs...>,
        Tape<Blank, xs...>
    >::type;
};

template<int, class>
class Right;

template<int pos, int... xs>
class Right<pos, Tape<xs...>> {
public:
    constexpr static int position = pos + 1;

    using tape = typename std::conditional<
        (pos < sizeof...(xs) - 1),
        Tape<xs...>,
        Tape<xs..., Blank>
    >::type;
};

#pragma mark - States

template <int>
class Stop {
public:
    constexpr static int write = -1;
    template<int pos, class tape> using move = Hold<pos, tape>;
    template<int x> using next = Stop<x>;
};

#define ADD_STATE(_state_)      \
template<int>                   \
class _state_ { };

#define ADD_RULE(_state_, _read_, _write_, _move_, _next_)          \
template<>                                                          \
class _state_<_read_> {                                             \
public:                                                             \
    constexpr static int write = _write_;                           \
    template<int pos, class tape> using move = _move_<pos, tape>;   \
    template<int x> using next = _next_<x>;                         \
};

#pragma mark - Machine

template<template<int> class, int, class>
class Machine;

template<template<int> class State, int pos, int... xs>
class Machine<State, pos, Tape<xs...>> {
    constexpr static int symbol = typename Read<pos, Tape<xs...>>::type();
    using state = State<symbol>;

    template<int x>
    using nextState = typename State<symbol>::template next<x>;

    using modifiedTape = typename Write<pos, state::write, Tape<xs...>>::type;
    using move = typename state::template move<pos, modifiedTape>;

    constexpr static int nextPos = move::position;
    using nextTape = typename move::tape;

public:
    using step = Machine<nextState, nextPos, nextTape>;
};

#pragma mark - Run

template<class>
class Run;

template<template<int> class State, int pos, int... xs>
class Run<Machine<State, pos, Tape<xs...>>> {
    using step = typename Machine<State, pos, Tape<xs...>>::step;

public:
    using type = typename std::conditional<
        std::is_same<State<0>, Stop<0>>::value,
        Tape<xs...>,
        Run<step>
    >::type::type;
};

ADD_STATE(A);
ADD_STATE(B);
ADD_STATE(C);
ADD_STATE(D);

ADD_RULE(A, Blank, 1, Right, B);
ADD_RULE(A, 1, 1, Left, B);

ADD_RULE(B, Blank, 1, Left, A);
ADD_RULE(B, 1, Blank, Left, C);

ADD_RULE(C, Blank, 1, Right, Stop);
ADD_RULE(C, 1, 1, Left, D);

ADD_RULE(D, Blank, 1, Right, D);
ADD_RULE(D, 1, Blank, Right, A);

using tape = Tape<Blank>;
using machine = Machine<A, 0, tape>;
using result = Run<machine>::type;

int main() {
    print(result());
    return 0;
}

Ideone preuve exécuter: https://ideone.com/MvBU3Z

explication: http://victorkomarov.blogspot.ru/2016/03/compile-time-turing-machine.html

GitHub avec plus d'exemples: https://github.com/fnz/CTTM

3
répondu Victor Komarov 2016-03-20 11:40:27

il est également amusant de souligner qu'il s'agit d'un langage purement fonctionnel, bien que presque impossible à déboguer. Si vous regardez James post vous verrez ce que je veux dire par Il étant fonctionnelle. En général, ce n'est pas la fonctionnalité la plus utile de C++. Il n'a pas été conçu pour cela. C'est quelque chose qui a été découvert.

2
répondu Matt Price 2017-05-23 12:02:56

il peut être utile si vous voulez calculer des constantes au moment de compiler, au moins en théorie. Découvrez modèle de la métaprogrammation .

2
répondu 2008-10-09 23:18:43

Un machine de Turing est Turing-complet, mais cela ne signifie pas que vous devriez vous voulez utiliser l'un pour la production de code.

essayer de faire n'importe quoi non-trivial avec des gabarits est dans mon expérience salissant, laid et inutile. Vous n'avez aucun moyen de" déboguer "votre" code", les messages d'erreur de compilation seront cryptés et généralement dans les endroits les plus invraisemblables, et vous pouvez obtenir les mêmes avantages de performance de différentes façons. (Indice: 4! = 24). Pire, votre code est incompréhensible pour le programmeur C++ moyen, et sera probablement non-portable en raison des niveaux de soutien très variés au sein des compilateurs actuels.

gabarits sont grands pour la génération de code générique (classes de conteneur, emballeurs de classe, mélanges), mais non-à mon avis, L'exhaustivité de Turing gabarits est pas utile dans la pratique.

1
répondu Roddy 2008-10-10 13:24:39

un exemple qui est raisonnablement utile est une classe de rapport. Il y a quelques variantes flottant autour. Attraper le cas D==0 est assez simple avec des surcharges partielles. Le calcul réel est dans le calcul de la DCG de N et D et le temps de compilation. C'est essentiel lorsque vous utilisez ces rapports dans les calculs de compilation.

exemple: lorsque vous calculez les centimètres(5)*les kilomètres(5), au moment de la compilation vous allez multiplier le rapport<1,100> et le rapport<1000,1>. De d'éviter un débordement, vous voulez un rapport<10,1> au lieu d'un ratio<1000,100>.

1
répondu MSalters 2008-10-10 14:29:56

juste un autre exemple de comment ne pas programmer:

template<int Depth, int A, typename B>
struct K17 {
    static const int x =
    K17 <Depth+1, 0, K17<Depth,A,B> >::x
    + K17 <Depth+1, 1, K17<Depth,A,B> >::x
    + K17 <Depth+1, 2, K17<Depth,A,B> >::x
    + K17 <Depth+1, 3, K17<Depth,A,B> >::x
    + K17 <Depth+1, 4, K17<Depth,A,B> >::x;
};
template <int A, typename B>
struct K17 <16,A,B> { static const int x = 1; };
static const int z = K17 <0,0,int>::x;
void main(void) { }

Post à les gabarits C++ sont complets

0
répondu lsalamon 2009-10-22 13:26:20