C ++ 11是否支持模板中的类型递归?

时间:2012-04-25 18:43:32

标签: c++ types c++11 type-theory

我想详细解释这个问题。在许多具有强类型系统的语言中(如Felix,Ocaml,Haskell),您可以通过组合类型构造函数来定义多态列表。这是Felix的定义:

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

在Ocaml中:

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

在C中,这是递归的,但既不是多态的也不是成分的:

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

在C ++中,如果C ++支持类型递归,它将像这样完成:

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 ++写这个&lt; C ++ 11因为没有模板typedef,没有办法获得多态性,并且没有typedef的合理语法,就无法在范围内获取目标类型。上面的using语法解决了这两个问题,但这并不意味着允许递归。

特别请注意,允许递归会对ABI产生重大影响,即在名称修改上(除非名称修改方案允许表示修复点,否则无法进行递归)。

我的问题:是否需要在C ++ 11中工作? [假设扩展不会导致无限大的结构]


编辑:为了清楚起见,要求是一般结构类型。模板提供了精确的,例如

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

是匿名的(结构上)类型,并且对显然是多态的。然而,C ++中的递归&lt;无法指定C ++ 11,即使使用指针也是如此。在C ++ 11中,您可以声明递归,虽然使用模板typedef(使用新的using语法,=符号的LHS上的表达式在RHS的范围内)。

使用多态和递归的结构(匿名)输入是类型系统的最低要求。

任何现代类型系统都必须支持多项式类型的仿函数,或者类型系统太过于无法进行任何类型的高级编程。为此所需的组合器通常由类型理论家说明:

1 | * | + | fix

其中1是单位类型,*是元组形成,+是变体形成,而修正是递归。这个想法很简单:

如果t是一个类型而u是一个类型,那么t + u和t * u也是类型

在C ++中,struct unit {}为1,tuple为*,variant为+,可以使用using =语法获取fixpoints。这不是一个非常匿名的输入,因为fixpoint需要一个模板typedef。


编辑:只是C:

中多态类型构造函数的一个例子
T*          // pointer formation
T (*)(U)    // one argument function type
T[2]        // array

不幸的是在C语言中,函数值不是组合的,而且指针的形成受左值约束的影响,类型组合的句法规则本身并不是组合的,但在这里我们可以说:

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>>>

并且语法是组合的,以及输入。

5 个答案:

答案 0 :(得分:9)

不,那是不可能的。即使是通过别名模板的间接递归也是禁止的。

C ++ 11,4.5.7 / 3:

  

别名模板声明中的type-id不应引用声明的别名模板。别名模板专业化生成的类型不得直接或间接地使用该专业化。 [例如:

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
     

- 结束示例]

答案 1 :(得分:9)

如果你想要这个,请坚持你的Felix,Ocaml或Haskell。你很容易意识到很少(没有?)成功语言的类型系统就像那三种一样丰富。在我看来,如果所有语言都相同,那么学习新语言就不值得了。

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

在C ++中不起作用,因为别名模板没有定义新类型。它纯粹是别名,同义词,等同于它的替换This is a feature, btw

该别名模板等同于以下Haskell:

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

GHCi拒绝这一点,因为“类型同义词声明中的[循环]”是不允许的。我不确定这是否在C ++中被彻底禁止,或者是否被允许但在替换时会导致无限递归。无论哪种方式,它都不起作用。

在C ++中定义新类型的方法是使用structclassunionenum关键字。如果你想要类似下面的Haskell(我坚持使用Haskell示例,因为我不知道其他两种语言),那么你需要使用这些关键字。

newtype List a = List (Either () (a, List a))

答案 2 :(得分:2)

我认为您可能需要检查您的类型理论,因为您的一些断言是不正确的。

让我们解决您的主要问题(以及反复点) - 正如其他人指出的那样,您所要求的类型的递归 是不允许的。这样做意味着c ++不支持类型递归。它非常好地支持它。您请求的类型递归是 name 递归,它是一种语法天赋,实际上对实际类型系统没有任何影响。

C ++允许通过代理进行元组成员递归。例如,c ++允许

class A
{
    A * oneOfMe_;
};

这是具有实际后果的类型递归。 (显然,没有内部代理表示,没有语言可以做到这一点,因为否则大小是无限递归的。)

此外,C ++允许使用translatetime多态,它允许创建与您使用名称递归创建的任何类型相同的对象。名称递归仅用于将类型卸载到成员或在类型系统中提供转换时行为分配。类型标签,类型特征等是众所周知的c ++惯用语。

要证明类型名称递归并不类型系统增加功能,它仅需要指出的是,C ++&#39;类型系统允许完全图灵完备型计算,采用上编译时常量元编程(通过名称到常量的简单映射,以及它们的类型列表。这意味着有一个函数MakeItC ++:YourIdeaOfPrettyName-&GT; TypeParametrisedByTypelistOfInts,让你想任何图灵computible类型系统

如您所知,作为类型理论的学生,变体是双元组产品。在类型类别中,变体的任何属性都具有元组产品的双重属性,箭头反转。如果您始终如一地使用二元性,则不会获得具有&#34;新功能的属性&#34; (在类型计算方面)。所以在类型计算的层面上,你显然不需要 变体。 (这也应该从图灵完整性中看出来。)

但是,就命令式语言中的运行时行为而言,您确实会遇到不同的行为。这是不好的行为。虽然产品限制语义,但变体放松了语义。你永远不应该想要这个,因为它证明破坏了代码的正确性。静态类型编程语言的历史已经朝着类型系统中语义的越来越大的表达方向发展,目标是编译器应该能够理解程序何时不代表你想要它的意思。目标是将编译器转变为程序验证系统。

例如,对于类型单位,您可以表示特定值不仅仅是int,而实际上是以米/平方秒为单位测量的加速度。分配一个速度值,以每小时英尺数表示的速度除以分钟的时间跨度,不应该只划分这两个值 - 它应该注意到转换是必要的(并且执行它或者编译失败或者......正确的事情)。分配力量应该无法编译。例如,对程序含义进行这些检查可能会给我们带来更多的火星探测。

变体是相反的方向。当然,&#34;如果您编码正确,它们可以正常工作&#34;,但这不是代码验证的重点。他们可以证明添加代码位置,其中不熟悉当前类型用法的不同工程师可以在没有翻译失败的情况下引入不正确的语义假设。并且,总有一个代码转换,从一个使用变体不安全到一个使用语义验证非变体类型的改变的命令性代码段,因此它们的使用也&#34;总是次优&#34;

变体的大多数运行时使用通常是在运行时多态中更好地封装的。运行时多态性具有静态验证的语义,可能具有关联的运行时不变检查,并且与变体(其中sum类型在一个代码轨迹中通用声明)不同,实际上支持Open-Closed原则。通过需要在一个位置声明变量,每次向总和添加新的函数类型时,都必须更改该位置。这意味着代码永远不会关闭更改,因此可能会引入错误。但是,运行时多态性允许在与其他行为不同的代码轨道中添加新行为。

(此外,大多数真正的语言类型系统无论如何都不是分配的。(a,b | c)= / =(a,b)|(a,c)那么这里有什么意义呢?)

如果没有在该领域获得一些经验,我会小心地做出关于什么使得类型系统变得优秀的一揽子陈述,特别是如果你的观点是挑衅性和政治性以及制定变革。我的帖子中没有看到任何实际指向任何计算机语言的健康变化的内容。我没有看到正在解决的功能,安全性或任何其他实际现实问题。我完全得到了类型理论的热爱。我认为每个计算机科学家都应该了解Cateogry Theory和编程语言的指称语义(领域理论,笛卡尔类别,所有好东西)。我认为如果更多的人将库里 - 霍华德同构视为一种本体论宣言,那么建构主义逻辑就会得到更多的尊重。

但这些都没有提供攻击c ++类型系统的理由。几乎每种语言都有合法的攻击 - 类型名称递归和变体可用性不是它们。


编辑:由于我的约图灵完整性点似乎不应该理解,也不是我关于C ++使用类型标记和性状卸载类型的计算的方式评论,也许一个例子是为了

现在OP声称要在列表的用例中使用它,我之前在布局上的点很容易处理。更好,只需使用std :: list。但是从其他评论和其他地方来看,我认为他们真的希望这可以用于Felix-&gt; C ++翻译。

所以,我认为OP认为他们想要的是

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

然后能够构建一个类型

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

我已经提到这只是一个想要的命名惯例。没有人想要类型名称 - 它们是翻译过程的瞬态。实际需要的是你将在类型的结构组合中使用Type做什么。它将在typename计算中用于生成成员数据和方法签名。

那么,在c ++中可以做的是

struct SelfTag {};

然后,当你想引用self时,只需将此类型标记放在那里。

如果进行类型计算有意义,那么SelfTag上的模板专精化将取代SomeClass<SelfTag>,而不是在类型的适当位置替换SelfTag计算

我的观点是,c ++类型系统是Turing Complete - 这意味着比我认为OP每次写入时都要读的更多。 任何类型计算都可以完成(给定编译器递归的约束)这确实意味着如果你在一个类型系统中遇到完全不同语言的问题,你可以在这里找到一个翻译即可。我希望这能使我的观点更加清晰。回过头来说,#34;你仍然不能在类型系统中做XYZ&#34;显然会忽略这一点。

答案 3 :(得分:0)

C ++确实有“奇怪的重复模板模式”或CRTP。但是,它不是特定于C ++ 11。这意味着您可以执行以下操作(从Wikipedia无耻地复制):

template <typename T>
struct base
{
    // ...
};
struct derived : base<derived>
{
    // ...
};

答案 4 :(得分:0)

@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提示想一想。实际的解决方案也与多年前Gabriel des Rois的通信有关。我要感谢所有贡献的人。