ChatGPT解决这个技术问题 Extra ChatGPT

有人告诉我,C++ 中的模板系统在编译时是图灵完备的。这在 this postwikipedia 中都有提及。

您能否提供一个利用此属性的计算的重要示例?

这个事实在实践中有用吗?


J
Johannes Schaub - litb

我用 C++11 做了一个图灵机。 C++11 添加的特性对于图灵机来说确实并不重要。它只是使用可变参数模板提供任意长度的规则列表,而不是使用不正当的宏元编程:)。条件名称用于在标准输出上输出图表。我已删除该代码以保持示例简短。

#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!");
}

你手头的时间太多了。
它看起来像 lisp,只是用一个特定的词替换了所有这些括号。
对于好奇的读者,完整的源代码是否可以在某个地方公开获得? :)
只是尝试值得更多的功劳:-) 这段代码编译(gcc-4.9)但没有输出 - 多一点信息,比如博客文章,会很棒。
@OllieFord 我在 pastebin 页面上找到了它的一个版本,并在此处重新粘贴: coliru.stacked-crooked.com/a/de06f2f63f905b7e
M
Martin York

例子

#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";
}

这有点有趣,但不是很实用。

回答问题的第二部分:这个事实在实践中有用吗?

简短的回答:有点。

长答案:是的,但前提是您是模板守护进程。

使用对其他人使用非常有用的模板元编程(即库)来产生良好的编程确实非常困难(尽管可行)。 To Help boost 甚至还有 MPL aka(元编程库)。但是尝试在你的模板代码中调试一个编译器错误,你将会经历一段漫长的艰难旅程。

但是一个很好的实际例子,它被用于有用的东西:

Scott Meyers 一直在使用模板工具对 C++ 语言进行扩展(我使用这个术语是松散的)。您可以在此处了解他的工作“Enforcing Code Features


该死的概念(噗)
我对提供的示例只有一个小问题——它没有利用 C++ 模板系统的(完整)图灵完备性。阶乘也可以使用原始递归函数找到,这些函数不是图灵完备的
a现在我们有了精简版的概念
在 2017 年,我们将概念推得更远。这是2020年的希望。
@MarkKegel 12 年后:D
M
Michael Mrozek

C++ Templates Are Turing Complete”在模板中给出了图灵机的实现......这很重要,并且以非常直接的方式证明了这一点。当然,它也不是很有用!


更好的链接:turing machine in c++1x
L
Luc Touraille

我的 C++ 有点生疏,所以可能并不完美,但已经很接近了。

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.

关键是要证明编译器正在完全评估递归定义,直到它得到答案。


嗯......你不需要在struct Factorial<0>之前的行上有“template<>”来表示模板专业化吗?
p
pizzapants184

举一个重要的例子: https://github.com/phresnel/metatrace ,一个 C++ 编译时光线追踪器。

请注意,C++0x 将以 constexpr 的形式添加非模板、编译时、图灵完备设施:

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

您可以在需要编译时常量的任何地方使用 constexpr-表达式,但您也可以使用非常量参数调用 constexpr-函数。

一件很酷的事情是,这最终将启用编译时浮点数学,尽管标准明确指出编译时浮点运算不必匹配运行时浮点运算:

bool f(){ 字符数组[1+int(1+0.2-0.1-0.1)]; //翻译时必须求值 int size=1+int(1+0.2-0.1-0.1); //可以在运行时计算 return sizeof(array)==size;未指定 f() 的值是真还是假。


y
yoav.aviram

Andrei Alexandrescu 的书 Modern C++ Design - Generic Programming and Design Pattern 是亲身体验有用且强大的通用编程模式的最佳场所。


佚名

阶乘示例实际上并没有表明模板是图灵完备的,而是表明它们支持原始递归。显示模板图灵完备的最简单方法是通过 Church-Turing 论文,即通过实现图灵机(混乱且有点无意义)或无类型 lambda 演算的三个规则(app、abs var)。后者更简单,也更有趣。

当您了解 C++ 模板允许在编译时进行纯函数式编程时,正在讨论的是一个非常有用的功能,这种形式主义具有表现力、强大和优雅,但如果您没有经验,编写起来也非常复杂。还要注意有多少人发现,仅仅获得大量模板化的代码通常需要付出很大的努力:(纯)函数式语言就是这种情况,这使得编译更加困难,但出人意料地产生了不需要调试的代码。


嘿,我想知道“app、abs、var”指的是哪三个规则?我假设前两个分别是函数应用程序和抽象(lambda 定义(?))。是这样吗?第三个是什么?与变量有关吗?
我个人认为,在编译器中支持 Primitive Recursion 的语言通常比 Turing Complete 更好,因为支持编译时 Primitive Recursion 的语言的编译器可以保证任何构建都将完成或失败,但是构建过程是图灵完备的人不能,除非通过人为地限制构建使其不是图灵完备。
T
Tom Ritter

我认为它称为template meta-programming


这是它有用的一面。不利的一面是,我怀疑大多数人(当然不是我)是否会真正理解其中大部分内容的一小部分。这是非常难以阅读、无法维护的东西。
我认为这是整个 C++ 语言的缺点。变成怪物了……
C++0x 承诺会让它变得更容易(根据我的经验,最大的问题是编译器不完全支持它,而 C++0x 无济于事)。尤其是概念看起来他们会理清思路,比如去掉很多难以阅读的 SFINAE 东西。
@MichaelBurr C++ 委员会不关心不可读、不可维护的东西;他们只是喜欢添加功能。
V
Victor Komarov

好吧,这是一个运行 4 状态 2 符号繁忙海狸的编译时图灵机实现

#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 证明运行:https://ideone.com/MvBU3Z

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

带有更多示例的 Github:https://github.com/fnz/CTTM


佚名

您可以查看 Dobbs 博士的这篇文章,了解使用模板的 FFT 实现,我认为这不是那么简单。要点是允许编译器执行比非模板实现更好的优化,因为 FFT 算法使用大量常量(例如 sin 表)

part I

part II


C
Community

指出它是一种纯粹的函数式语言也很有趣,尽管几乎不可能调试。如果您查看 James 帖子,您会明白我所说的功能性是什么意思。一般来说,它不是 C++ 最有用的特性。它不是为此而设计的。这是被发现的东西。


佚名

如果您想在编译时计算常量,它可能会很有用,至少在理论上是这样。查看template metaprogramming


M
MSalters

一个相当有用的例子是比率类。周围有一些变体。通过部分重载,捕获 D==0 的情况相当简单。真正的计算是在计算 N 和 D 的 GCD 和编译时间。当您在编译时计算中使用这些比率时,这是必不可少的。

示例:当您计算厘米(5)*公里(5)时,在编译时您将乘以 ratio<1,100> 和 ratio<1000,1>。为了防止溢出,您需要一个 ratio<10,1> 而不是 ratio<1000,100>。


R
Roddy

Turing machine 是图灵完备的,但这并不意味着您应该将其用于生产代码。

根据我的经验,尝试用模板做任何不重要的事情都是混乱、丑陋和毫无意义的。您无法“调试”您的“代码”,编译时错误消息将是神秘的并且通常在最不可能的地方,并且您可以通过不同的方式获得相同的性能优势。 (提示:4!= 24)。更糟糕的是,您的代码对于普通的 C++ 程序员来说是不可理解的,并且由于当前编译器的广泛支持水平,您的代码可能是不可移植的。

模板非常适合通用代码生成(容器类、类包装器、混入),但不是——在我看来,模板的图灵完整性在实践中没有用。


4!可能是 24 岁,但 MY_FAVORITE_MACRO_VALUE 是多少! ?好吧,我实际上也不认为这是一个好主意。
l
lsalamon

只是另一个如何不编程的例子:

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) { }

C++ templates are turing complete 发帖


对于好奇的人,x 的答案是 pow(5,17-depth);
当您意识到模板参数 A 和 B 什么都不做并删除它们,然后用 K17<Depth+1>::x * 5 替换所有添加时,这更容易看到。