模板与泛型的区别

What makes a template different from a generic?

本文关键字:区别 泛型      更新时间:2023-10-16

我了解c++中模板与Java和c#中的泛型不同的方面。c#是一种具体化,Java使用类型擦除,c++使用鸭子类型,等等。c++模板可以做很多Java和c#泛型不能做的事情(比如模板专门化)。但是,有许多Java泛型可以做的事情,c#和c++做不到(例如,为一系列泛型(如class Foo<T extends Comparable<?>>)创建有界类型参数),并且有许多c#泛型可以做的事情,Java和c++做不到(例如,运行时泛型反射)。[编辑:显然Java泛型比我想象的要弱得多。](这说明了一些问题。)在任何情况下,尽管它们很笨拙,它们仍然和c#的泛型一样被认为是泛型[/em>

我不明白的是什么在概念上使模板不同于泛型。c++模板的哪些部分是不能在非模板的泛型中完成的?例如,如果我要实现一种支持模板的语言,其中绝对需要什么?对于语言支持泛型,我可以省略哪些必要的内容?

我的猜测是模板是泛型的超集,或者它们是实现泛型的一种方式,但我真的不明白真正的模板和真正的泛型之间的区别。

嗯…如果你说你对c++模板有深入的了解,并说你没有看到/感觉到泛型和它们之间的区别,那么,很可能你是对的:)

有许多不同之处可以描述泛型如何/为什么比模板更好,列出了大量的不同之处,等等,但这大多与思想的核心无关。

这个想法是为了允许更好的代码重用。模板/泛型为您提供了一种构建某种高阶类定义的方法,这些定义抽象于某些实际类型之上。

在这种情况下,它们之间没有区别,唯一的区别是由底层语言和运行时的特定特性和约束所强制执行的。

有人可能会争辩说泛型提供了一些额外的特性(通常是在谈论对象类树的动态自省时),但是很少有(如果有的话)不能在c++的模板中手动实现。通过一些努力,它们中的大多数都可以实现或模拟,因此它们不能很好地区分"适当的泛型"answers"真正的模板"。

其他人会争辩说,由于c++的复制-粘贴行为,优化的纯粹潜在力量是不同的。对不起,不是这样的。Java和c#中的jit也可以做到这一点,很好,差不多,但做得非常好。

然而,有一件事确实可以使Java/c#的泛型成为c++模板特性的真正子集。你居然还提过!

模板特化

在c++中,每个特化的行为都是一个完全不同的定义。

在c++中,专用于T==int的template<typename T> Foo可能看起来像:

class Foo<int> 
{
    void hug_me();
    int hugs_count() const;
}

而专化为T==MyNumericType的"相同"模板可能看起来像

class Foo<MyNumericType> 
{
    void hug_me();
    MyNumericType get_value() const;
    void  reset_value() const;
}

仅供参考:这只是伪代码,不会编译:)

Java和c#的泛型都不能做到这一点,因为它们的定义声明所有泛型类型的具体化将具有相同的"用户界面"。

更重要的是,c++使用了一个SFINAE规则。一个模板可能存在许多"理论上冲突的"专门化定义。然而,当模板被使用时,只有那些"真正好的"才会被使用。 对于类似于上面例子的类,如果使用:
 Foo<double> foood;
 foood.reset_value();

只使用第二个专门化,因为第一个专门化不能编译,因为…"reset_value"失踪。

对于泛型,您不能这样做。你需要创建一个具有所有可能方法的泛型类,然后在运行时动态检查内部对象,并为不可用方法抛出一些"未实现"或"不支持"异常。这是……非常糟糕的事。这些在编译时应该是可能的。 模板专门化SFINAE的实际功能、含义、问题和整体复杂性是真正区分泛型和模板的地方。简单地说,泛型是这样定义的,专门化是不可能的,因此SFINAE是不可能的,因此,整个机制矛盾地更容易/更简单。

在编译器内部更容易/更简单地实现,也更容易被非专业人士理解。

虽然我同意泛型在Java/c#中的总体好处,但我真的很想念特殊化、接口灵活性和SFINAE规则。然而,如果我不提及与相同的OO设计相关的一件重要事情,我将是不公平的:如果类型xxx的模板专门化实际上改变了它的客户端API,那么很可能它应该以不同的方式命名,并应该形成不同的模板。模板所能做的所有额外的好处都被添加到工具集中,因为……在c++中没有反射,必须以某种方式进行模拟。SFINAE是一种编译时反射。

因此,差异世界中最大的玩家被简化为一个奇怪的(有益的)副作用,用于掩盖运行时的缺陷,这是几乎完全缺乏运行时自省:))

因此,我说除了一些由语言强制执行的任意规则,或者一些由运行时平台强制执行的任意规则之外,没有什么区别。

它们都是高阶类或函数/方法的一种形式,我认为这是最重要的东西和特性。

首先,我发现RTTI/自省是大多数答案的重要组成部分,这很有趣。这不是泛型和模板的区别,而是有内省的语言和没有内省的语言的区别。否则,你也可以声称这是c++类与Java类的区别,c++函数与Java函数的区别…

如果不考虑内省,主要区别在于模板定义了一种完整的语言,函数式的风格,尽管语法很糟糕,你可以在上面编程。我听说的第一个真正复杂的例子(我很想知道代码,但我没有)是一个在编译时计算素数的程序。这确实带来了另一个区别:模板可以接受类型参数,或模板参数或非类型参数(非类型指的是任何不是类型或模板的东西,如int值)。

这在其他答案中已经提到过,但是仅仅说模板可以专门化并且存在SFINAE并没有清楚地说明这两个特性足以生成图灵完整的语言。

有许多Java泛型可以做c#和c++不能做的事情(例如,创建一个一类泛型如class Foo<T extends Comparable<?>>)的有界类型参数

这个例子并不完全正确:

template <typename Comparable>
struct Foo {
    static bool compare(const Comparable &lhs, const Comparable &rhs) {
        return lhs == rhs;
    }
};

只有当模板形参是相等可比类型时,这个类模板才会成功实例化compare函数。它不叫"有界类型参数",但它的作用是一样的。

如果在c++中你想把Comparable当作一个显式接口(即一个基类)而不是一个鸭子类型的概念,那么你可以把static_assert(is_base_of<Comparable, T>::value, "objects not Comparable");,或者其他什么。

不,模板不是泛型的超集,对于c++模板,你没有与c#泛型相同级别的运行时支持,这意味着c++中的RTTI无法像c#中的反射对泛型那样检测和提供模板的元数据。

除了

,我喜欢这个片段:

c++模板使用编译时模型。模板在c++程序,其效果就像一个复杂的宏处理器被使用。

c#泛型不仅是编译器的一个特性,也是一个特性运行时的。泛型类型(如List)维护其编译后的泛型(泛型)。或者,看换句话说,c++编译器在compile时做的替换在c#泛型世界中,time是在JIT时间完成的。

查看全文:c#泛型与c++模板相比如何?

这是一个旧的线程,我的代表太低了,不能评论接受的答案,但我想补充:

除了显式特化之外,c++模板和c#泛型的另一个关键区别是c++中使用的非类型模板形参:

template<int bar> class Foo {};
Foo<1> a;
Foo<2> b;
a = b; //error, different types. 

非类型模板形参可以是任何整数类型、枚举以及可以在编译时确定的指针(静态存储变量和函数指针)。在c++ 20中,它们也可以是类类型,但有一定的限制。

c#和Java泛型都做不到。

也可以显式专门化非类型参数。

作为旁注,D编程语言使用术语"模板"作为泛型编程的命名法,至少对我来说,它的特性在精神上更接近c++,而不是c#/Java。

我不知道为什么c#中没有非类型参数的技术原因,但是作为一种我最近比其他语言使用得更多的语言,我偶尔会想念这个特性。

我将把我的答案限制在c++模板与Java泛型之间。

  1. c++模板(类模板和函数模板)是实现编译时多态性,但Java泛型是运行时的机制。
  2. 使用c++模板,你可以做泛型编程,它实际上是是完全独立的编程风格/范例,但Java泛型是OO风格本身。见下文:
  3. c++模板是基于Duck类型的,而Java泛型是基于类型擦除。在c++中,vector<int>, vector<Shape *>, vector<double>vector<vector<Matrix>>是4种不同的类型,但是在Java中Cell<int>, Cell<Integer>Cell<double>Cell<Matrix>是同一类型。更准确地说,是在代码生成期间编译器首先擦除类型。您可以通过以下代码检查它:弗拉基米尔•Batov。Java泛型和c++模板。C/c++ Users Journal, July 2004.

    public class Cell<E>
    {
       private E elem;
       public Cell(E elem)
       { 
          this.elem = elem;
       }
       public E GetElement()
       { 
          return elem;
       }
       public void SetElement(E elem)
       { 
          this.elem = elem;
       } 
    }
    boolean test()
    { 
      Cell<Integer> IntCell(10); 
      Cell<String> StrCell(“Hello”);
      return IntCell.getClass() ==
             StrCell.getClass(); // returns true
    }
    

简而言之,Java假装是泛型的,而c++实际上是。