C++中的模板是否都用于元编程?

15

我想了解什么是元编程以及它在一般情况下的含义,以及在C++中的具体表现。如果我搜索c++元编程,我会得到关于模板元编程(TMP)的教程,但没有解释是否它只对特定用途的模板进行分类还是所有用途的模板都包括在内。

我的问题是是否将C++中所有用途的模板都归类为元编程。对于为什么是或者不是的解释也会有所帮助。谢谢。


1
与CL宏进行比较,并查看格林斯潘第十条规则。https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule - stark
2
TMP不是元编程的唯一方式。人们也可以使用宏(嗯)和constexpr函数进行元编程。与TMP相比,constexpr更容易实现。 - zett42
2
@zett42 我不确定仅在编译时执行代码是否应被视为“元编程”。因为那不是编程,而是计算。它只是将执行从运行时转移到编译时。C++中的元编程与此不同,因为它生成代码(模板、宏)。 - Johannes Schaub - litb
4个回答

6
我的问题是,C ++ 中所有使用模板的用法是否都归类为元编程。
不是的。
并非所有在C++中使用模板的用法都是元编程。
显然,这是一个定义问题,但在C++中,“元编程”等同于“编译时计算”。
因此,我们使用模板进行元编程(具体来说是模板元编程),但并非所有模板的用法都是元编程。
一个简单的反例
template <typename K, typename V>
void printKeyVal (K const & k, V const & v)
 { std::cout << k << ": " << v << std::endl; }

printKeyVal()是一个模板函数,其作用是将一些通用值打印到标准输出(因此是运行时,而不是编译时)。

由于它不能在编译时运行,因此被称为“模板”,但并不是“元编程”。

更一般地说,std::vector是一个使用内存分配的模板类。而内存分配(直到C++17;可能在未来会有所改变)不能用于编译时代码。

因此,std::vector(与具有固定大小且不使用内存分配的std::array相反)是一个模板特性,当使用实例化了std::vector对象时无法用于元编程。


然而,任何依赖于模板的函数都需要至少一些元编程。在您的示例中,printKeyVal 根据模板参数在编译时定义。 - SomeWittyUsername
1
当你编程时,你写代码。你不会“计算”东西。因此,我将“元编程”定义为“编写代码来编写代码”。这正是每个模板所关注的。我知道模板元编程经常用于“编译时计算”之类的事情,但我个人会使用不同的术语(我确定我以前没有这样做,所以请原谅)。 - Johannes Schaub - litb
1
@JohannesSchaub-litb - 这是语言和定义的问题(而且我还有一个额外的劣势,就是我不太懂英语),我同意“元编程”的定义可能包括所有模板,这也许是合理的。但我想说的是,在C++社区中,“元编程”这个术语被用于与所有模板使用相交但并不包括所有模板使用的不同含义。也许我错了...这可能是一项有趣的调查。 - max66
也许只有当模板的代码不再是每种类型的1:1副本时,它才被称为“元编程”,因为如果它是1:1副本,那么模板代码就是纯被动的,所有的“元编程”都是由编译器完成的。如果有一些特化、静态if、重载分派或其他东西,那么就是代码本身决定将生成什么。这似乎是D语言的立场:https://tour.dlang.org/tour/en/gems/template-meta-programming - Johannes Schaub - litb
@SomeWittyUsername - 在我看来,你使用的“元编程”定义是完全合法且也许合理的,但与C++社区实际使用的定义不同:粗略地说:“元编程==编译时执行”。也许我错了,社区主要使用包括所有模板用法的定义...这可能是一个有趣的调查,我想。 - max66

4

C++中的TMP是什么?

C++中的模板元编程(TMP)是一种使用C++模板在编译时表达和执行任意算法的技术。它通常通过使用模板特化来模拟条件分支和递归模板定义来模拟循环来实现。最著名的例子是编译时阶乘计算:

template <unsigned int n>
struct factorial {
    // recursive definition to emulate a loop or a regular recursion
    enum { value = n * factorial<n - 1>::value };
};

// specialization that describes "break" condition for the recursion
template <>
struct factorial<0> { 
    enum { value = 1 };
};

这个技术同时使用了上述两种技术。

然而,更为常见的是使用TMP进行类型检测和转换,而不是进行实际的数值计算,例如标准的std::is_pointer工具(source):

// generic definition that emulates "false" conditional branch
template<class T>
struct is_pointer_helper : std::false_type {};

// a specialization that emulates "true" conditional branch
template<class T>
struct is_pointer_helper<T*> : std::true_type {};

template<class T>
struct is_pointer : is_pointer_helper< typename std::remove_cv<T>::type > {};

标准type_traits头文件提供的大多数实用程序都是使用TMP技术实现的。

鉴于TMP算法是使用类型定义表示的,值得一提的是,TMP是一种声明式编程形式,其中计算逻辑是不使用显式控制流语句(ifelsefor等)来表达的。

C++模板的所有用法都是元编程吗?

简短的回答是:不是。如果模板未用于表达编译时算法,则它不是元编程,而是泛型编程

在C++中引入模板的主要目的是实现泛型编程,即允许重用相同的算法(如findcopysort等)和数据结构(如vectorlistmap等)对于任何类型,包括用户定义的类型,只要这些类型满足某些要求。
实际上,在C++中TMP是意外发现的,并不是模板的预期使用方式。
总之:C++中的模板元编程是使用模板来表达编译时算法,C++模板的所有(或大多数)其他用途都属于泛型编程。

1
我想了解元编程的一般概念,以及在C++中的具体应用。您还没有说明您对于“元编程”的理解,因此我们缺少共同的起点。 我将采用维基百科对元编程的定义wikipedia definition: 元编程是一种编程技术,在这种技术中,计算机程序能够将其他程序作为数据来处理,可以使用元编程将计算从运行时移到编译时,通过编译时计算生成代码等。 C++通常不允许自修改代码,因此我将忽略它。 我还选择不考虑预处理器,因为在(或可以说是接近)编译时进行的文本替换与操作程序语义并不相同。 不是所有C++模板的用法都归类为元编程。 请参考:
#define MAX(a,b) ((a) > (b) ? (a) : (b))

这个函数是一种通用的(与类型无关的)max函数的写法,没有使用模板。我已经说过,我不认为预处理器是元编程,但无论如何,每次使用它时它总是生成相同的代码。
它只是简单地委托给稍后的翻译阶段来解析该代码,并担心类型和是否定义了a>b到编译器。这里没有任何东西在编译时运行以根据...任何内容产生不同的结果代码。在编译时,没有计算任何内容。
现在,我们可以比较模板版本:
template <typename T>
T max(T a, T b) { return a > b ? a : b; }

这并不仅仅是一个文本替换操作。实例化的过程更加复杂,需要考虑名称查找规则和函数重载问题,并且从某种意义上讲,不同的实例化可能在文本上并不等价(例如,一个实例化可能使用bool ::operator<(T,T),而另一个实例化可能使用bool T::operator<(T const&)或其他方式)。

然而,每个实例化的含义相同(假设不同类型之间定义了兼容的operator<),除编译器通常处理类型、名称解析等机械过程外,在编译时没有计算任何内容。

顺便说一下,你的程序包含指令,告诉编译器该执行什么操作是绝对不够的,因为所有的编程都是如此。

现在,还有一些边角案例:

template <unsigned N>
struct factorial() { enum { value = N * factorial<N-1>::value }; };

这些操作确实将计算移动到编译时(在这种情况下是一个非终止计算,因为我无法编写终止情况),但可以说它们不是元编程。

尽管维基百科的定义提到了将计算移动到编译时,但这只是一个值计算 - 它没有对代码的结构或语义做出编译时决策。


-1
当使用模板编写C++函数时,您正在为编译器编写“指令”,告诉它在遇到函数调用时要做什么。从这个意义上说,您并没有直接编写代码,因此我们称之为元编程。
因此,涉及模板的每个C++代码都被认为是元编程。
请注意,只有定义模板函数或类的部分被归类为元编程。常规函数和类被归类为常规C++!

1
当我在Bash中输入“cp a.txt b.txt”时,尽管我使用了完整的编程环境,但这并不是编程。同样,当我使用std::vector时,我指示编译器生成一个程序(因此它确实是元编程),但是这个指令本身几乎不是一个程序。大多数人不认为使用宏是“元编程”。 - n. m.
1
你的答案完全错误!请阅读本帖中给出的其他答案,以获得 MTP 的基本理解! - Klaus
1
每当你在编写没有模板的C++代码时,实际上是在为编译器编写指令。因此,仅仅通过模板告诉编译器该做什么这一事实,并不足以区分编程和元编程。 - Useless
1
@JohannesSchaub-litb 在C++的上下文中,“元编程”一词具有特定的含义,与其一般含义不同。如果我没记错的话,它最初是指A.Alexandrescu在他的书《现代C++设计》中发现的C++特性。在C++中,模板的经典用法并不是元编程(除非您使用非C++术语“元编程”)。 - YSC
1
从OP的问题中可以看出:“我正在尝试理解元编程在一般情况下是什么,以及在C++中是什么”。所以我猜你的角度很好,会构成一个很好的答案。但这个不是。-1 - YSC
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接