C ++ 'daki şablon sisteminin derleme zamanında Turing-tamamlandığı söylendi. Bundan bu yazıda ve ayrıca wikipedia'da bahsedilmektedir .
Bu özelliği kullanan bir hesaplamanın önemsiz bir örneğini verebilir misiniz?
Bu gerçek pratikte faydalı mı?
C ++ 'daki şablon sisteminin derleme zamanında Turing-tamamlandığı söylendi. Bundan bu yazıda ve ayrıca wikipedia'da bahsedilmektedir .
Bu özelliği kullanan bir hesaplamanın önemsiz bir örneğini verebilir misiniz?
Bu gerçek pratikte faydalı mı?
Yanıtlar:
Misal
#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";
}
Bu biraz eğlenceliydi ama pek pratik değildi.
Sorunun ikinci bölümünü yanıtlamak için:
Bu gerçek pratikte yararlı mı?
Kısa Cevap: Sırala.
Uzun Cevap: Evet, ancak yalnızca bir şablon arka plan programı iseniz.
Başkalarının kullanması (yani bir kitaplık) için gerçekten yararlı olan şablon meta programlamayı kullanarak iyi bir programlamayı ortaya çıkarmak gerçekten çok zordur (ancak yapılabilir). Arttırmaya yardımcı olmak için MPL aka (Meta Programlama Kitaplığı) bile vardır . Ancak şablon kodunuzdaki bir derleyici hatasını ayıklamayı deneyin ve uzun ve zorlu bir yolculuğa çıkacaksınız.
Ancak yararlı bir şey için kullanılmasının iyi bir pratik örneği:
Scott Meyers şablon oluşturma olanaklarını kullanarak C ++ dilinin uzantıları üzerinde çalışıyor (bu terimi gevşek bir şekilde kullanıyorum). Sen burada çalışmaları hakkında okuyabilir Zorlamayı Kod Özellikleri '
C ++ 11'de bir turing makinesi yaptım. C ++ 11'in eklediği özellikler turing makinesi için gerçekten önemli değildir. Sadece, sapkın makro metaprogramlama kullanmak yerine, değişken şablonlar kullanarak keyfi uzunluk kural listeleri sağlar :). Koşulların adları, standart çıktıda bir diyagram oluşturmak için kullanılır. örneği kısa tutmak için bu kodu kaldırdım.
#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!");
}
" C ++ Templates Are Turing Complete ", şablonlarda bir Turing makinesinin uygulamasını verir ... bu önemsiz değildir ve konuyu çok doğrudan bir şekilde kanıtlar. Tabii ki çok da kullanışlı değil!
Benim C ++ programım biraz paslı, bu yüzden mükemmel olmayabilir, ama yakın.
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.
Önemli olan, derleyicinin bir yanıta ulaşana kadar özyinelemeli tanımı tamamen değerlendirdiğini göstermektir.
Önemsiz olmayan bir örnek vermek gerekirse: http://gitorious.org/metatrace , bir C ++ derleme zamanı ışın izleyicisi.
C ++ 0x'in şablonsuz, derleme zamanı, turing-tamamlama tesisi şu şekilde ekleyeceğini unutmayın constexpr
:
constexpr unsigned int fac (unsigned int u) {
return (u<=1) ? (1) : (u*fac(u-1));
}
constexpr
-İfadeyi derleme zamanı sabitlerine ihtiyaç duyduğunuz her yerde kullanabilirsiniz , ancak constexpr
const olmayan parametrelerle -fonksiyonlar da çağırabilirsiniz .
Harika bir şey, bunun sonunda zaman kayan noktalı matematiği derlemeyi mümkün kılacak olmasıdır, ancak standart, zaman kayan noktalı aritmetik derlemenin çalışma zamanı kayan nokta aritmetiği ile eşleşmek zorunda olmadığını açıkça belirtir:
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; }
F () değerinin doğru mu yanlış mı olacağı belirtilmemiştir.
Andrei Alexandrescu'nun Yazdığı Modern C ++ Tasarımı - Genel Programlama ve Tasarım Modeli Kitabı , kullanışlı ve güçlü genel programlama kalıplarıyla uygulamalı deneyim kazanmak için en iyi yerdir.
Faktöriyel örnek aslında şablonların İlkel Özyinelemeyi desteklediklerini gösterdiği kadar Turing'in tamamlandığını göstermez. Şablonların tamamlandığını göstermenin en kolay yolu Church-Turing tezidir, yani ya bir Turing makinesi (dağınık ve biraz anlamsız) ya da türlenmemiş lambda hesabının üç kuralını (app, abs var) uygulamaktır. İkincisi çok daha basit ve çok daha ilginç.
Tartışılan şey, C ++ şablonlarının derleme zamanında saf işlevsel programlamaya izin verdiğini anladığınızda son derece yararlı bir özelliktir; ifade edici, güçlü ve zarif ama aynı zamanda az deneyiminiz varsa yazmak için çok karmaşık bir biçimcilik. Ayrıca, kaç kişinin sadece yoğun bir şekilde şablon haline getirilmiş kod almanın genellikle büyük bir çaba gerektirebileceğini düşündüğüne dikkat edin: Bu, derlemeyi zorlaştıran ancak şaşırtıcı bir şekilde hata ayıklama gerektirmeyen kod üreten (saf) işlevsel dillerde durumdur.
Sanırım buna şablon meta programlama deniyor .
İşte 4 durumlu 2 sembollü meşgul kunduz çalıştıran bir derleme zamanı Turing Machine uygulaması
#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;
}
İdeon prova çalışması: https://ideone.com/MvBU3Z
Açıklama: http://victorkomarov.blogspot.ru/2016/03/compile-time-turing-machine.html
Daha fazla örnek içeren Github: https://github.com/fnz/CTTM
O kadar da önemsiz olmadığını düşündüğüm şablonlarla FFT uygulaması hakkında Dr. Dobbs'un bu makalesine göz atabilirsiniz. Temel nokta, derleyicinin şablon olmayan uygulamalardan daha iyi bir optimizasyon gerçekleştirmesine izin vermektir çünkü FFT algoritması çok sayıda sabit kullanır (örneğin sin tabloları)
Hata ayıklaması neredeyse imkansız olsa da, tamamen işlevsel bir dil olduğunu belirtmek de eğlenceli. James gönderisine bakarsanız, işlevsel olmakla ne demek istediğimi göreceksiniz. Genel olarak C ++ 'nın en kullanışlı özelliği değildir. Bunu yapmak için tasarlanmadı. Keşfedilen bir şey.
En azından teoride sabitleri derleme zamanında hesaplamak istiyorsanız yararlı olabilir. Şablon meta programlamasına göz atın .
Makul derecede kullanışlı bir örnek, bir oran sınıfıdır. Etrafta yüzen birkaç varyant var. D == 0 durumunu yakalamak, kısmi aşırı yüklenmelerle oldukça basittir. Gerçek hesaplama, N ve D'nin OBEB ve derleme zamanını hesaplamaktır. Derleme zamanı hesaplamalarında bu oranları kullandığınızda bu çok önemlidir.
Örnek: Santimetre (5) * kilometre (5) hesaplarken, derleme zamanında <1.100> oranı ve <1000,1> oranını çarpacaksınız. Taşmayı önlemek için <1000,100> oranı yerine <10,1> oranını istiyorsunuz.
Bir Turing makinesi Turing-tamamlanmıştır, ancak bu, üretim kodu için bir tane kullanmak istemeniz gerektiği anlamına gelmez.
Şablonlarla önemsiz olmayan bir şey yapmaya çalışmak benim deneyimime göre dağınık, çirkin ve anlamsız. "Kodunuzda" "hata ayıklama" yapmanın bir yolu yoktur, derleme zamanı hata mesajları şifreli olacaktır ve genellikle en olası olmayan yerlerde ve aynı performans avantajlarını farklı şekillerde elde edebilirsiniz. (İpucu: 4! = 24). Daha da kötüsü, kodunuz ortalama bir C ++ programcısı tarafından anlaşılmaz ve mevcut derleyicilerdeki çok çeşitli destek seviyeleri nedeniyle muhtemelen taşınabilir olmayacaktır.
Şablonlar genel kod üretimi için harikadır (kapsayıcı sınıfları, sınıf paketleyicileri, karışımlar), ancak hayır - bence şablonların Turing Tamlığı pratikte YARARLI DEĞİL .
Nasıl programlanmayacağına dair bir başka örnek:
şablon <int Derinlik, int A, tür adı B> struct K17 { statik sabit int x = K17 <Derinlik + 1, 0, K17 <Derinlik, A, B>> :: x + K17 <Derinlik + 1, 1, K17 <Derinlik, A, B>> :: x + K17 <Derinlik + 1, 2, K17 <Derinlik, A, B>> :: x + K17 <Derinlik + 1, 3, K17 <Derinlik, A, B>> :: x + K17 <Derinlik + 1, 4, K17 <Derinlik, A, B>> :: x; }; şablon <int A, tür adı B> yapı K17 <16, A, B> {statik sabit int x = 1; }; statik sabit int z = K17 <0,0, int> :: x; void main (void) {}
Biz Post C ++ şablonları tam turing edilir
K17<Depth+1>::x * 5
.