Поддерживает ли С++ 11 рекурсию типов в шаблонах?

Я хочу подробно объяснить вопрос. Во многих языках со строгой системой типов (таких как Felix, Ocaml, Haskell) вы можете определить полиморфный список, составив конструкторы типов. Вот определение Феликса:

typedef list[T] = 1 + T * list[T];
typedef list[T] = (1 + T * self) as self;

В Окамле:

type 'a list = Empty | Cons ('a, 'a list)

В C это рекурсивно, но не полиморфно и не композиционно:

struct int_list { int elt; struct int_list *next; };

В С++ это было бы сделано так, если бы С++ поддерживал рекурсию типов:

struct unit {};
template<typename T>
    using list<T> = variant< unit, tuple<T, list<T>> >;

дано подходящее определение для кортежа (также известного как пара) и варианта (но не сломанного, используемого в Boost). В качестве альтернативы:

    using list<T> = variant< unit, tuple<T, &list<T>> >;

может быть приемлемым, учитывая немного другое определение варианта. Невозможно было написать это даже на C++ ‹ C++11, потому что без шаблонных определений типов невозможно получить полиморфизм, а без нормального синтаксиса для определений типов невозможно получить целевой тип в области видимости. Приведенный выше синтаксис using решает обе эти проблемы, однако это не означает, что рекурсия разрешена.

В частности, обратите внимание, что разрешение рекурсии сильно влияет на ABI, т. е. на изменение имени (это невозможно сделать, если схема изменения имени не допускает представления фиксированных точек).

Мой вопрос: требуется ли для работы в С++ 11? [Предполагая, что расширение не приводит к бесконечно большой структуре]


Редактировать: просто чтобы быть ясным, требуется общая структурная типизация. Шаблоны обеспечивают именно это, например

pair<int, double>
pair<int, pair <long, double> >

анонимно (структурно) типизированы, и пара явно полиморфна. Однако рекурсия в C++ ‹ C++11 не может быть указана даже с помощью указателя. В С++ 11 вы можете указать рекурсию, хотя и с помощью шаблона typedef (с новым синтаксисом using выражение в левой части знака = находится в области действия в правой части).

Структурная (анонимная) типизация с полиморфизмом и рекурсией — минимальные требования к системе типов.

Любая современная система типов должна поддерживать функторы полиномиальных типов, иначе система типов будет слишком громоздкой для любого вида программирования высокого уровня. Комбинаторы, необходимые для этого, обычно указываются теоретиками типов, например:

1 | * | + | fix

где 1 — тип модуля, * — формирование кортежа, + — формирование варианта, а fix — рекурсия. Идея просто в том, что:

если t является типом и u является типом, то t + u и t * u также являются типами

В C++ struct unit{} — это 1, кортеж — это *, variant — это +, а точки фиксации могут быть получены с использованием синтаксиса =. Это не совсем анонимная типизация, потому что для fixpoint потребуется определение типа шаблона.


Изменить: просто пример конструктора полиморфного типа в C:

T*          // pointer formation
T (*)(U)    // one argument function type
T[2]        // array

К сожалению, в C значения функций не являются композиционными, и формирование указателя зависит от ограничения lvalue, а синтаксические правила для композиции типов сами по себе не являются композиционными, но здесь мы можем сказать:

if T is a type T* is a type
if T and U are types, T (*)(U) is a type
if T is a type T[2] is a type

поэтому эти конструкторы типов (комбинаторы) могут применяться рекурсивно для получения новых типов без необходимости создания нового промежуточного типа. В C++ мы можем легко исправить синтаксическую проблему:

template<typename T> using ptr<T> = T*;
template<typename T, typename U> using fun<T,U> = T (*)(U);
template<typename T> using arr2<T> = T[2];

так что теперь вы можете написать:

arr2<fun<double, ptr<int>>>

и синтаксис композиционный, как и набор текста.


person Yttrill    schedule 25.04.2012    source источник
comment
Чего именно вы пытаетесь достичь?   -  person Kerrek SB    schedule 25.04.2012
comment
Если вы начинаете вопрос с на приличном языке, вы можете получить немного тепла... Во всяком случае, вы должны указать, что вы хотите сделать, рекурсия типов делает на самом деле не звоните в колокол относительно того, чего вы хотите достичь. Что вы имеете в виду под полиморфным и композиционным?   -  person David Rodríguez - dribeas    schedule 25.04.2012
comment
@KerrekSB: структурная типизация с полиморфизмом, именно для этого были разработаны шаблоны, но они никогда не поддерживали рекурсию типов, что очень важно.   -  person Yttrill    schedule 25.04.2012
comment
@DavidRodríguez-dribeas: Очевидно, он хочет добиться создания рекурсивных псевдонимов шаблонов. Однако вопрос можно было бы сформулировать менее резко.   -  person jpalecek    schedule 25.04.2012
comment
вариант (но не сломанный, используемый в Boost) Каким образом он сломан? Тот факт, что он реализован на C++, что вам не нравится?   -  person ildjarn    schedule 25.04.2012
comment
@ildjarn: последний раз, когда я смотрел, вариант Boost не работает, потому что он допускает только один вариант каждого типа, поэтому это неправильный тип суммы (варианта). Варианты должны допускать любое количество случаев с одинаковыми типами аргументов. Самый тривиальный пример варианта — перечисление, для которого вообще нет аргументов.   -  person Yttrill    schedule 25.04.2012
comment
Он не претендует на то, чтобы быть правильным типом суммы (варианта), поэтому он не сломан, он просто не делает того, что вы хотите.   -  person ildjarn    schedule 25.04.2012
comment
@ildjam: это правда, но не я налагаю требования, а теория типов. Неспособность кортежа Boost реализовать правильный тип кортежа — это именно то, что побудило разработчиков поместить вариативные шаблоны в стандарт C++11. В этом смысле кортеж был сломан, но теперь его можно правильно реализовать. В этом смысле вариант остается сломанным.   -  person Yttrill    schedule 25.04.2012
comment
@Yttrill: этот вопрос был бы очень интересным, если бы вы вытащили все гиперболы и ерунду о требованиях для возможности выполнять любое программирование высокого уровня. Как бы то ни было, это просто приманка   -  person jalf    schedule 25.04.2012
comment
@Yttrill: Что касается вариантов, достаточно честно; то, как это сформулировано в вашем вопросе, довольно резко, но ваша разработка более разумна.   -  person ildjarn    schedule 25.04.2012
comment
@jalf: Иногда полезно быть немного резким. Проблемы с программированием не являются полностью техническими. Они тоже политические.   -  person Yttrill    schedule 26.04.2012
comment
Ваш вопрос был бы понятнее, если бы вы перестали смешивать два отдельных понятия. Первый: могу ли я выразить этот тип в системе типов? Ответ прост: нет, система типов C++ слишком проста, чтобы изначально поддерживать то, что вы хотите. Больше ничего. Второй: как этот тип может быть реализован? Это совсем другой вопрос, и он зависит от конкретного конкретного случая, а не от гипотетического вопроса. Ваша проблема, кажется, в том, что вы думаете, что они должны происходить одновременно. Какую проблему ты пытаешься решить?   -  person GManNickG    schedule 26.04.2012
comment
@Yttrill Я полагаю, вы хотите привлечь технически квалифицированных специалистов? Тогда режьте политическое дерьмо, потому что оно их только оттолкнет.   -  person R. Martinho Fernandes    schedule 26.04.2012
comment
@Yttrill: нет. Это было бы политически и потенциально полезно) быть резким в комитете по стандартам C++. Обращение к случайным программистам только для того, чтобы сказать им, что C++ отстой, потому что у него нет тех же целей разработки, что и у Haskell, кажется менее полезным. Попытка замаскировать языковую войну под вопросом, который мог бы быть законным, бесполезна и неполитична.   -  person jalf    schedule 26.04.2012
comment
@Yttrill: проблемы с программированием не совсем технические. Они тоже политические. Резко говорить о политическом вопросе еще хуже. Резкость в отношении щекотливой, личной темы только заставляет людей перестать слушать то, что вы хотите сказать.   -  person Nicol Bolas    schedule 26.04.2012
comment
@Everyone: замечание о том, что принято быть абразивным.   -  person Yttrill    schedule 27.04.2012
comment
@GManNickG: Это был простой ответ, на который вы ссылаетесь, я хотел, я просто не знал. Я написал Бьерну по электронной почте, но он не ответил. Что касается реализации типа, мне нужен механический способ генерации имен для полиномиальных типов. Я уже генерирую типы, это легко. Проблема в том, что сгенерированные имена различаются в двух единицах компиляции: у меня нет способа детерминировано генерировать достаточно короткие читаемые имена из типов. Я надеялся использовать имена шаблонов и позволить программе обработки имен C++ делать свое дело.   -  person Yttrill    schedule 27.04.2012
comment
Чтобы быть более конкретным для list‹int›, я генерирую struct _1675 { int mem0; _1675 *mem1; } из функциональной спецификации. Нет проблем, за исключением того, что в следующей единице перевода я генерирую struct _2312 { int mem0; _2312 *mem1; }, и поэтому такая функция, как int add(_1675), не может быть разделена между единицами перевода, потому что имена типов различаются.   -  person Yttrill    schedule 27.04.2012
comment
Почему вы говорите, что вариант Boost сломан?   -  person user2023370    schedule 27.10.2012
comment
Потому что, насколько я могу судить, он допускает только один случай для каждого типа. Реальный вариант не имеет этого ограничения. Кроме того, его использование - это супер PITA по сравнению с реальным вариантом, особенно потому, что C ++ не имеет сопоставления с образцом.   -  person Yttrill    schedule 29.10.2012


Ответы (5)


Нет, это невозможно. Запрещена даже непрямая рекурсия через шаблоны псевдонимов.

C++11, 4.5.7/3:

Идентификатор типа в объявлении шаблона псевдонима не должен ссылаться на объявляемый шаблон псевдонима. Тип, созданный специализацией шаблона псевдонима, не должен прямо или косвенно использовать эту специализацию. [ Пример:

template <class T> struct A;
template <class T> using B = typename A<T>::U;
template <class T> struct A {
typedef B<T> U;
};
B<short> b; // error: instantiation of B<short> uses own type via A<short>::U

— конец примера]

person jpalecek    schedule 25.04.2012
comment
Мне грустно. Можете ли вы случайно процитировать текст стандарта С++ 11, который исключает это? Я надеюсь, что есть конкретное исключение, потому что, когда экспериментальные реализации заставляют работать, есть простая цель для последующего ослабления ограничения. Это, безусловно, можно сделать: мой язык программирования Felix может это сделать, и он генерирует C++. На самом деле, моя основная проблема заключается в том, что для таких типов нет канонического имени, что делает динамическую связь довольно сложной. - person Yttrill; 25.04.2012

Если вы хотите этого, придерживайтесь своего Felix, Ocaml или Haskell. Вы легко поймете, что очень немногие (ни одного?) успешные языки имеют такие же богатые системы типов, как эти три. И на мой взгляд, если бы все языки были одинаковыми, учить новые не стоило бы.

template<typename T>
using list<T> = variant< unit, tuple<T, list<T>> >;

В C++ не работает, потому что шаблон псевдонима не определяет новый тип. Это просто псевдоним, синоним, и он эквивалентен его замене. Это функция, кстати.

Этот шаблон псевдонима эквивалентен следующему фрагменту Haskell:

type List a = Either () (a, List a)

GHCi отклоняет это, потому что «[циклы] в объявлениях синонимов типов» не разрешены. Я не уверен, что это прямо запрещено в С++ или разрешено, но вызывает бесконечную рекурсию при замене. В любом случае, это не работает.

Способ определения новых типов в C++ — это ключевые слова struct, class, union и enum. Если вы хотите что-то вроде следующего Haskell (я настаиваю на примерах Haskell, потому что я не знаю двух других языков), вам нужно использовать эти ключевые слова.

newtype List a = List (Either () (a, List a))
person R. Martinho Fernandes    schedule 25.04.2012
comment
Интересно, Haskell не поддерживает структурную типизацию? Я не программист на Haskell (хотя я могу кое-что прочитать :) На самом деле C++ имеет очень богатую и исключительно мощную систему типов, даже если она немного сломана. Например, типизированные смещения, указатели, массивы и классы на более низком уровне и подлинное, хотя и очень плохо типизированное полиадическое программирование (функторный полиморфизм). STL фактически полагается на это (обобщение структур данных с использованием итераторов). - person Yttrill; 26.04.2012
comment
@Yttrill Нет, Haskell не поддерживает структурную типизацию. Возможно, есть расширение GHC, которое помогает, но я понятия не имею. - person R. Martinho Fernandes; 27.04.2012
comment
Просто убедитесь, что я понимаю, что такое структурная типизация, newtype Foo a = Foo (Either () (a, Foo a)) будет определять тип, отличный от типа List a в последнем примере в моем ответе, потому что у них разные имена. Передача одного в функцию, ожидающую другого, является ошибкой. Насколько я понимаю, при структурной типизации они будут одинаковыми, потому что они имеют одинаковую структуру и могут использоваться взаимозаменяемо. Пожалуйста, поправьте меня, если я ошибаюсь. - person R. Martinho Fernandes; 27.04.2012
comment
да. Конечно, в Haskell есть некоторая структурная типизация, потому что List int — это имя типа. Однако функтор данных List может быть только номинально типизирован. Грубо говоря, я думаю, что структурная типизация — это просто вопрос канонических имен для типовых выражений. - person Yttrill; 27.04.2012
comment
+1 Лучше принятого ответа. Этот пост фактически объясняет с теоретической точки зрения, почему это не может работать и почему это не работает в Haskell таким же образом. - person Silvio Mayolo; 28.02.2016

Я думаю, вам, возможно, придется пересмотреть свою теорию типов, так как некоторые из ваших утверждений неверны.

Давайте ответим на ваш главный вопрос (и двусмысленный момент) - как указывали другие, рекурсия типа запрошенного вами типа не разрешена. Это не означает, что C++ не поддерживает рекурсию типов. Он отлично поддерживает его. Запрашиваемая вами рекурсия типа — это рекурсия типа name, представляющая собой синтаксическую особенность, которая на самом деле никак не влияет на реальную систему типов.

C++ допускает рекурсию членства в кортеже через прокси. Например, С++ позволяет

class A
{
    A * oneOfMe_;
};

Это рекурсия типов, которая имеет реальные последствия. (И, очевидно, ни один язык не может сделать это без внутреннего прокси-представления, потому что в противном случае размер бесконечно рекурсивен).

Также C++ допускает полиморфизм времени трансляции, что позволяет создавать объекты, действующие как любой тип, который вы можете создать с помощью рекурсии имен. Рекурсия имени используется только для выгрузки типов в члены или предоставления назначений поведения во время перевода в системе типов. Теги типов, признаки типов и т. д. являются хорошо известными идиомами С++ для этого.

Чтобы доказать, что рекурсия имени типа не добавляет функциональности системе типов, нужно только указать, что система типов С++ позволяет полностью вычислять типы по Тьюрингу, используя метапрограммирование для констант времени компиляции (и их списков типов), посредством простого отображения имена констант. Это означает, что существует функция MakeItC++:YourIdeaOfPrettyName->TypeParametrisedByTypelistOfInts, которая создает любую вычислимую по Тьюрингу систему типов, которую вы хотите.

Как вы знаете, изучая теорию типов, варианты двойственны произведениям кортежей. В категории типов любое свойство вариантов имеет двойственное свойство кортежных произведений с перевернутыми стрелками. Если вы работаете последовательно с дуальностью, вы не получаете свойств с «новыми возможностями» (с точки зрения вычислений типов). Таким образом, на уровне вычислений типов вам явно не нужны нужны варианты. (Это также должно быть очевидно из полноты по Тьюрингу.)

Однако с точки зрения поведения во время выполнения на императивном языке вы получаете другое поведение. И это плохое поведение. В то время как продукты ограничивают семантику, варианты ослабляют семантику. Вы никогда не должны хотеть этого, так как это доказуемо разрушает правильность кода. История статически типизированных языков программирования движется в направлении все большего и большего выражения семантики в системе типов с целью, чтобы компилятор мог понять, когда программа означает не то, что вы хотите. Цель состояла в том, чтобы превратить компилятор в систему проверки программы.

Например, с помощью единиц измерения вы можете указать, что конкретное значение — это не просто int, но фактически ускорение, измеряемое в метрах в секунду в квадрате. Присвоение значения, представляющего собой скорость, выраженную в футах в час, деленную на промежуток времени в минутах, не должно просто делить два значения — должно быть указано, что преобразование необходимо (и либо выполнить его, либо не выполнить компиляцию, либо... сделать правильно вещь). Присвоение силы должно привести к сбою компиляции. Такого рода проверки смысла программы могли бы, например, потенциально дать нам больше возможностей для исследования Марса.

Варианты противоположного направления. Конечно, «если вы кодируете правильно, они работают правильно», но это не главное в проверке кода. Они доказуемо добавляют локусы кода, где другой инженер, незнакомый с текущим использованием типов, может ввести неверное семантическое предположение без ошибки перевода. И всегда есть преобразование кода, которое изменяет императивный раздел кода с того, который небезопасно использует варианты, на тот, который использует семантически проверенные невариантные типы, поэтому их использование также «всегда субоптимально».

Большинство вариантов во время выполнения обычно лучше инкапсулируются в полиморфизме во время выполнения. Полиморфизм времени выполнения имеет статически проверенную семантику, которая может иметь связанную проверку инвариантов времени выполнения, и в отличие от вариантов (где тип суммы универсально объявлен в одном месте кода) фактически поддерживает принцип Open-Closed. Из-за необходимости объявлять вариант в одном месте, вы должны менять это место каждый раз, когда добавляете в сумму новый функциональный тип. Это означает, что код никогда не закрывается для внесения изменений и, следовательно, может содержать ошибки. Однако полиморфизм во время выполнения позволяет добавлять новые поведения в отдельные локусы кода, отличные от других поведений.

(Кроме того, большинство реальных языковых систем типов в любом случае не являются дистрибутивными. (a, b | c) =/= (a, b) | (a, c), так какой же здесь смысл?)

Я был бы осторожен, делая общие заявления о том, что делает систему типов хорошей, не имея некоторого опыта в этой области, особенно если ваша цель состоит в том, чтобы быть провокационным и политическим и проводить изменения. Я не вижу в вашем посте ничего, что на самом деле указывало бы на здоровые изменения для любого компьютерного языка. Я не вижу, чтобы функции, безопасность или какие-либо другие реальные проблемы реального мира решались. Я полностью проникся любовью к теории типов. Я думаю, что каждый ученый-компьютерщик должен знать теорию категорий и денотационную семантику языков программирования (теорию предметной области, декартовы категории и все такое прочее). Я думаю, что если бы больше людей понимало изоморфизм Карри-Ховарда как онтологический манифест, конструктивистская логика пользовалась бы большим уважением.

Но ничто из этого не дает причин атаковать систему типов С++. Почти для каждого языка существуют законные атаки — рекурсия имен типов и доступность вариантов не являются таковыми.


РЕДАКТИРОВАТЬ: Поскольку моя точка зрения о полноте Тьюринга, похоже, не понята, как и мой комментарий о способе С++ использования тегов типов и признаков для разгрузки вычислений типов, возможно, пример в порядке.

Теперь ОП утверждает, что хочет этого в случае использования списков, с которым легко справляется мой предыдущий пункт в макете. Лучше просто используйте std::list. Но из других комментариев и других источников я думаю, что они действительно хотят, чтобы это работало над переводом Felix-> C++.

Итак, я думаю, что ОП думает, что они хотят, что-то вроде

template <typename Type>
class SomeClass
{
    // ...
};

а затем иметь возможность построить тип

SomeClass< /*insert the SomeClass<...> type created here*/ >

Я упомянул, что это всего лишь соглашение об именах. Никому не нужны имена типов — они являются переходными процессами процесса перевода. Что на самом деле требуется, так это то, что вы сделаете с Type позже в структурной композиции типа. Он будет использоваться в вычислениях имени типа для создания данных членов и сигнатур методов.

Итак, что можно сделать в С++, это

struct SelfTag {};

Затем, когда вы захотите сослаться на себя, просто поместите туда этот тег типа.

Когда имеет смысл выполнить вычисление типа, у вас есть специализация шаблона на SelfTag, которая заменит SomeClass<SelfTag> вместо замены SelfTag в соответствующем месте вычисления типа.

Я хочу сказать, что система типов С++ является завершенной по Тьюрингу, и это означает гораздо больше, чем то, что я думаю, что ОП читает каждый раз, когда я это пишу. Вычисление любого типа может быть выполнено (учитывая ограничения рекурсии компилятора) и это действительно означает, что если у вас есть проблема в одной системе типов на совершенно другом языке, вы можете найти перевод здесь . Я надеюсь, что это сделает мою точку зрения еще более ясной. Вернуться назад и сказать: «Ну, вы все еще не можете использовать XYZ в системе типов», было бы явно упущено главное.

person ex0du5    schedule 26.04.2012
comment
Возможно, вам следует перечитать мой вопрос. Проблема не в том, разрешает ли C++ рекурсию типов, я сам указал, что даже C делает это. Вопрос в том, позволяют ли это шаблоны, а они нет: в частности, требуется полиморфизм, компонуемость в смысле комбинаторов и рекурсия. - person Yttrill; 26.04.2012
comment
Также вы ошибаетесь в отношении полиморфизма времени выполнения в двух разных смыслах. Варианты закрыты, а модификация ломает вещи, чего и можно желать. Благодаря этому мой код Ocaml работает без ошибок (при условии, что я не использую подстановочные знаки). OTOH, в то время как диспетчеризация виртуальных функций, безусловно, обеспечивает открытое/закрытое поведение, ее применимость ограничена атрибутами из-за проблемы ковариации, поэтому это не общее решение: полиморфизм виртуальных функций НЕ РАБОТАЕТ для большинства интересных задач. - person Yttrill; 26.04.2012
comment
Что касается необходимости вариантов, я думаю, вы неправильно понимаете, что нужно программистам (как и почти все программисты). Ассемблера явно достаточно, чтобы закодировать что угодно: потребность определяется не вычислительными возможностями, а тем, как это выражается. Однако я спрашиваю, можете ли вы подробнее объяснить свою точку зрения о безопасности вариантов? - person Yttrill; 26.04.2012
comment
@Yttrill: шаблоны допускают рекурсию того же типа, о котором я упоминал. Вот почему я не использовал шаблоны, чтобы не запутать вопрос. Вы ищете рекурсию имени типа, которая полезна только в вычислениях типов для построения реальных типов. C++ имеет полную систему вычисления типов. Полный. Как и в каждом типе расчета, который вы могли бы захотеть. Не с тем же синтаксисом имени типа, который вы хотите, а со своим собственным. Вы не просите ничего невозможного, вам просто не нравится, как это делается. - person ex0du5; 26.04.2012
comment
@Yttrill: я указываю на то, как развивались системы типов (в обеспечении семантики над проблемой простой компоновки сборки), и вы предполагаете, что я не понимаю, почему сборки недостаточно? Вы перестали думать о том, что я сказал, и начали отвечать инстинктивно, защищаясь. Я не могу спорить с вами, если вы не читаете то, что я пишу. У вас есть комментарий по поводу семантического применения систем типов, или вы просто собираетесь ответить другими словами, которые вызывают у вас хорошее настроение, но не имеют ничего общего с моей точкой зрения? - person ex0du5; 26.04.2012
comment
На самом деле я ищу способ структурно создавать рекурсивные типы. Вы можете создавать продукты на C и C++ со структурами. Конечно. Но вы также можете сделать их структурно с помощью шаблона кортежа‹›: я думаю, тогда имена полей будут просто целыми числами. Мне нужно сделать рекурсию так же: структурно. Почему? Потому что я не хочу, чтобы структурные типы в двух единицах перевода согласовывались с их каноническими именами, иначе они не могут быть связаны друг с другом. - person Yttrill; 27.04.2012
comment
Кстати, я уверен, что вы знаете больше теории типов, чем я. Однако отсутствие рекурсии типов в шаблонах определенно является причиной для атаки на систему типов С++ ИМХО. Все преимущество шаблонов заключается в возможности их компоновки. Вместо list_int вы можете использовать list‹int› без объявления нового типа. Но он недостаточно общий: он даже не может параметризовать то, что может сделать C: C выполняет рекурсию по неполным типам, которые позже завершаются. Шаблоны тоже могут и должны разрешать это, но они этого не делают (я имею в виду параметры типа, а не определения классов). - person Yttrill; 27.04.2012
comment
@Yttrill: я отредактировал свой ответ, указав фактическое решение для ваших конкретных потребностей, чтобы показать вам, что нет упущенных преимуществ компонуемости и что шаблоны комментариев могут и должны разрешать это, но они не ошибаются. - person ex0du5; 27.04.2012
comment
не вижу, как это помогает. И, кстати, я знал, что шаблоны Тьюринг завершены задолго до вас: я физически присутствовал на собрании стандартов ISO, где это было радостно обнаружено экспериментальным путем. - person Yttrill; 28.04.2012
comment
@exOdu5: не могли бы вы отправить мне электронное письмо skaller на пользователя dot sourceforge dot n e t - person Yttrill; 28.04.2012
comment
упс, это пользователи не пользователи... извините - person Yttrill; 28.04.2012

C++ действительно имеет «любопытно повторяющийся шаблон шаблона» или CRTP. Однако это не относится к С++ 11. Это означает, что вы можете сделать следующее (бессовестно скопированное из Википедии):

template <typename T>
struct base
{
    // ...
};
struct derived : base<derived>
{
    // ...
};
person Julian    schedule 25.04.2012
comment
Да, это позволяет отложить завершение, как это делает C struct X, где X еще не определен. Это обеспечивает рекурсию, но вы должны использовать именованные типы (номинальную типизацию). Если бы вы могли сделать это для систематической реализации рекурсивного полиморфного типа, было бы неплохо, но ваш производный класс является рекурсивным экземпляром, он не полиморфен, поэтому вы не можете писать интересные полиморфные алгоритмы (шаблонные функции). - person Yttrill; 25.04.2012
comment
@Yttrill: так что вы не можете писать интересные полиморфные алгоритмы (шаблонные функции): это не так уж плохо. Вы не можете писать типизированные универсальные функции с рекурсивными типами, но у вас все равно нет универсальных типов, а шаблоны не типизированы, так что кого это волнует. На самом деле вы просто берете любой полученный тип и используете его через известный интерфейс. Итак, написание функций, которые принимают типы T, где возможно T <: base<T>. - person jpalecek; 26.04.2012
comment
На самом деле это так плохо, к сожалению. ПО МОЕМУ МНЕНИЮ. Позвольте задать вам интересную задачу (которая, собственно, и является моей актуальной проблемой): для любого полиномиального типа дайте мне каноническое имя. Например, для 1 + T * x as x, который является функциональным списком, имя, которое я хотел бы использовать, является прямым сопоставлением с шаблонами: template<class T, class X> fix<X, variant2 < unit, tuple2<T,X>>>. На самом деле меня волнует только имя в моем приложении: я делаю рекурсию типов, я просто не могу связать одни и те же типы в единицах перевода. - person Yttrill; 26.04.2012
comment
Просто чтобы объяснить вышеизложенное: мне нужно систематически генерировать имена для структурных типов. Я не могу написать код, который работает, я должен написать код, который пишет код. Конечно, если бы С++ действительно поддерживал рекурсию типов в шаблонах, мой транслятор был бы менее полезен, потому что это один из недостатков С++, который он призван исправить. - person Yttrill; 27.04.2012

@jpalcek ответил на мой вопрос. Однако моя реальная проблема (как указано в примерах) может быть решена без рекурсивных псевдонимов, например:

// core combinators
struct unit;
struct point;
template<class T,class U> struct fix;
template<class T, class U> struct tup2;
template<class T, class U> struct var2;

template <> struct   
  fix<
    point,
    var2<unit, tup2<int,point> > 
  > 
{ 
  // definition goes here
};

используя типы fix и point для представления рекурсии. Мне не нужно определять какие-либо шаблоны, мне нужно только определить специализации. Мне нужно было имя, которое было бы одинаковым в двух разных единицах перевода для внешней связи: имя должно было быть функцией структуры типа.

@ Ex0du5 заставил задуматься об этом. Фактическое решение также связано с перепиской Габриэля де Руа много лет назад. Я хочу поблагодарить всех, кто внес свой вклад.

person Yttrill    schedule 28.04.2012
comment
Не могли бы вы привести простой пример того, как вы будете использовать этот код? - person user2023370; 26.10.2012
comment
Моя проблема состоит в том, чтобы получить уникальное имя для произвольной комбинации и вариантов, включая рекурсию (поскольку они описывают очень широкий набор полезных типов данных). Таким образом, определение приведенного выше будет следующим: { struct fix‹point,var2‹unit,tup2‹uint,point››› *next; точечные данные; } - person Yttrill; 27.10.2012
comment
Это просто односвязный список (единица измерения + точка * self) как self, который является кодом, из которого генерируется вышеуказанное. Я не могу назвать его списком, потому что это просто выражение типа. - person Yttrill; 27.10.2012
comment
Я уверен, что у вас есть представление о том, какое применение имеют односвязные списки, они являются одним из основных типов, используемых в функциональном программировании. Существует множество таких типов, большинство из которых можно определить в одной строке. Бинарное дерево — это просто 1 + T * self * self как self. И т. д. В совокупности эти типы называются полиномиальными типами. - person Yttrill; 27.10.2012
comment
Гррр.. выше следует читать сочетание кортежей и вариантов. - person Yttrill; 27.10.2012
comment
Это использует 5 комбинаторов выше? Это 2 определения? я получаю сообщение об ошибке; point, имеет неполный тип. - person user2023370; 28.10.2012
comment
Должен быть struct unit {}; структурная точка {}; и int должен быть единицей. Если вы хотите понять варианты, посмотрите на Ocaml, Haskell или Felix. Все они имеют правильные варианты и сопоставление с образцом. - person Yttrill; 29.10.2012
comment
Почему next и data объявляются в составе составного оператора? - person user2023370; 30.10.2012
comment
Это не составной оператор, это часть отображаемого кода между { и }, где говорится, что // здесь находится определение. Итак, это объявление двух членов структуры. Извините за путаницу, эти комментарии недостаточно длинны, чтобы объяснить. - person Yttrill; 30.10.2012
comment
Должен ли класс point иметь члены? В любом случае, спасибо за разъяснение. Итак, я думал, вы хотели избежать указателей? Почему ваша система fix отличается от старой доброй: struct Abc { Abc *next; int data; }; ? Кроме того, вам нужно ключевое слово struct в определении? - person user2023370; 31.10.2012
comment
Откуда взялось имя Абс? Это проблема! У нас есть структура данных, определяемая составом частей: list = var2‹unit, tup‹int,list››. Если эта структура данных используется в двух единицах компиляции, эти два экземпляра должны иметь одно и то же внешнее (искаженное) имя C++. Программист не называет структуру данных, точно так же, как он не называет вектор C++ типа int, который называется vector‹int›, так же как мой называется fix‹var2‹unit, tup‹int,point›››. Программист на C++ может использовать псевдоним: typedef basic_string‹char› строка, но строка не является внешним именем. - person Yttrill; 01.11.2012