元编程是一个非常奇特的话题。了解它很有趣,它是一个强大的工具,有时您可能会发现它很有用。但它永远不会是您工具箱中最常用的工具。有时,您可能希望您的代码作用于一系列具有不同属性的不相关类型,这就是元编程的用武之地。通过一些技巧,您可以编写一个函数的重载,该函数仅在参数类型是整数时才可用,或者如果它是一个指针,或者它是 X、Y 或 Z 类型(可能忽略 Z 上的常量)。
本质上是使用类型进行编程。通常,您的程序可以执行诸如取两个数字并产生第三个数字之类的操作,或者告诉您一个数字是否满足某些要求。元编程可以采用两种类型并产生第三种类型,或者告诉您一种类型是否满足某些要求。是的,它可能在图书馆开发中最有用。但话又说回来,大多数代码可以被认为是库代码。您可以说 main() 函数之外的所有内容都是库代码。
通常,如果您想通过元编程解决问题,您可能需要使用相关的 boost 库来完成繁重的工作。 Boost.TypeTraits 当然还有 Boost.Mpl 可以真正为你简化事情。但这不是你需要知道的东西,也不是你可能经常需要的东西。
泛型编程是相关的(在某些情况下,可能会在底层使用元编程来变得真正泛型,例如,标准库使用元编程将原始指针转换为有效的迭代器,这是“迭代器”概念所必需的是通用的),但并不完全相同。而且使用范围更广。
每次实例化std::vector 时,都会使用泛型编程。每次您使用一对迭代器来处理一系列值时,您就使用了泛型编程。泛型编程就是你的代码应该尽可能通用,并且无论放入什么类型都应该工作。 std::vector 不需要包含的类型来实现“ICanBeContained”接口(请记住 Java 是如何要求所有内容都从 Object 派生以便将其存储在容器类中?这意味着原始类型被装箱,并且我们失去了类型安全。这不是通用的,而且是毫无意义的限制。)
使用迭代器迭代序列的代码是通用的,并且适用于任何类型的迭代器,甚至可以使用普通指针。
泛型编程非常有用,通常可以在很大程度上替代 OOP。 (见上面的例子。如果我能避免这个限制,我为什么要编写一个需要包含的类型来实现接口的容器?)
通常,当您在 OOP 中使用接口时,不允许类型在运行时更改(当然这种情况有时也会发生),而是允许您在编译时交换另一种类型(也许在测试期间注入一个模拟对象,而不是使用成熟的实现),或者只是为了解耦两个类。通用编程可以做到这一点,而无需您进行定义和维护接口的繁琐工作。在这些情况下,泛型编程意味着您必须编写和维护更少的代码,并且您可以获得更好的性能和更好的类型安全性。所以,是的,你绝对应该对泛型编程感到宾至如归。 C++ 不是一个很好的 OOP 语言。如果您想严格遵守 OOP,则应该切换到 Java 或其他更偏向于 OOP 的语言。 C++ 允许您编写 OO 代码,但它通常不是最佳解决方案。几乎整个标准库都依赖于泛型编程,而不是 OOP,这是有原因的。标准库中几乎没有继承或多态性。他们不需要它,没有它,代码变得更简单易用,更强大。
要回答您的其他问题,是的,通用编程几乎是一个独立的范例。模板元编程不是。它是一种用于操作类型系统的相当具体的技术,并且非常擅长解决少数问题。要被认为是一种范式,我认为它必须更普遍有用,并且您可以使用基本上一切的方法,例如函数式、OO 或泛型编程。
我认为 xtofl 确实做到了:泛型编程就是让您的代码不识别类型。 (std::vector 不关心,或者需要知道其中存储了什么类型。它只是工作。)
另一方面,元编程是关于类型计算的。给定类型 T0 和 T1,我们可以定义一个类型 T2,就像我们可以这样,给定整数 N0 和 N1,我们可以定义一个 N2,它是 N0 和 N1 的和。
Boost.Mpl 库有一个明显的例子。
在您的正常代码中,如果您有整数 N0、N1 和 N2,您可以创建一个包含这三个值的 std::vector。然后我可以使用其他算法来计算索引,然后提取存储在向量中该位置的值。
给定类型 T0、T1 和 T2,我们可以创建一个包含这三个类型的 mpl::vector。
我现在可以使用其他算法在编译时计算索引,并提取存储在向量中该位置的 type。