TL;DR 没耐心或没时间的可以往下滚,直接看结尾处的总结部分。
问题背景
如果我告诉你,部分特化严格来说不能算是特化,你会不会感到震惊?
众所周知,模板特化(template specialization)有两种形式:完全特化(full specialization)和部分特化(partial specialization)。两种形式都相当常见。比如,这是一段来自标准库的代码(改编自 Clang 的标准库 libc++,有简化):
// 主模板
template <typename T>
struct hash {};
// 对 char 的完全特化
template <>
struct hash<char> {
size_t operator()(char c) const noexcept { return c; }
};
// 对 signed char 的完全特化
template <>
struct hash<signed char> {
size_t operator()(signed char c) const noexcept { return c; }
};
// 对 unsigned char 的完全特化
template <>
struct hash<unsigned char> {
size_t operator()(unsigned char c) const noexcept { return c; }
};
第一个 hash
是主模板的定义,但里面并没有给出实际的内容(如何计算哈希)。下面跟着的是三个完全特化,给出了所有模板参数,分别对 char
、signed char
和 unsigned char
类型给出了如何计算该类型对象的哈希的方法。所有希望支持哈希的类型都需要自己来进行完全特化。
下面是另一段来自标准库的代码:
// 主模板
template <class T>
struct is_array : public false_type {};
// 部分特化,规定了传进来的类型必须是 T[] 的形式
template <class T>
struct is_array<T[]> : public true_type {};
// 部分特化,规定了传进来的类型必须是 T[N] 的形式
template <class T, size_t N>
struct is_array<T[N]> : public true_type {};
类似地,第一个 is_array
是主模板的定义,说明对于任何类型,默认用 is_array
判断的结果是 false
(因为 false_type
里的 value
成员的值定义为 false
)。后面两个则是部分特化,没有给定所有模板参数,而是给出了对于模板参数的约束,在这里分别针对长度不确定的数组类型和长度确定为某个整数的数组类型,规定用 is_array
判断的结果是 true
(因为 true_type
里的 value
成员的值定义为 true
)。
部分特化怎么可能不是特化呢?
标准视角
这个问题,实际上反映了语言的复杂性和 C++ 里概念的复杂性,以及一个词在不同上下文里形成的冲突用法。我们先来看一下 C++ 标准里的用法。
我查了一下从 C++98 到 C++23 的标准,发现里面的用词还是相当一致的。不过,首先可以看到的是,标准里基本没有使用 full specialization 一词(我目前只见到一处),而是使用了 explicit specialization(显式特化)。explicit specialization、partial specialization 和 specialization 在 C++23 草案 [N4950] 中的定义分别如下:
- An explicit specialization … can be declared by a declaration introduced by
template<>
… (13.9.4 [temp.expl.spec]/1)
显式特化……可以通过以template<>
开头的声明来声明…… - A partial specialization of a template provides an alternative definition of the template that is used instead of the primary definition when the arguments in a specialization match … (13.7.6.1 [temp.spec.partial.general]/1)
模板的部分特化为模板提供了一个替代定义,当特化的参数匹配……[后面会描述具体的匹配]时,该定义将替代主模板定义使用。 - A specialization is a class, variable, function, or class member that is either instantiated (13.9.2) from a templated entity or is an explicit specialization (13.9.4) of a templated entity. (13.9.1 [temp.spec.general]/4)
特化是指一个类、变量、函数或类成员,它要么从模板实体实例化(13.9.2)而来,要么是模板实体的显式特化(13.9.4)。
这里很明确:从 C++ 标准的角度,显式(完全)特化是特化,而部分特化并不符合特化的定义,因而不是特化。
日常用法和用法变迁
那……我们的用词是不是都跟标准一致呢?
最早的“特化”用法
显然不是,我们日常说到特化时,往往是完全特化和部分特化的合称,而不是标准里的意思——尤其是,通常不包含从主模板通过显式或隐式实例化生成的结果。这种用法至少从 C++ 之父 1994 年的作品里就有了 [Stroustrup 1994]:
Instead, specialization can be used to provide separate versions for specific template argument types (§15.10.3).
与此相反的方法是,可以使用特化来对各种特殊的模板参数类型提供各自的版本(15.10.3 节)。I therefore concluded that we needed a mechanism for “specializing” templates. This could be done either by accepting general overloading or by some more specific mechanism. I chose a specific mechanism because I thought I was primarily addressing irregularities caused by irregularities in C and because suggestions of overloading invariably creates a howl of protests. I was trying to be cautious and conservative; I now consider that a mistake. Specialization as originally defined was a restricted and anomalous form of overloading that fitted poorly with the rest of the language.
因此,我的总结是,需要有一种能对模板“特化”的机制。可以通过接受一般的重载而达到这个目的,或是通过某种更特殊的机制。我选择使用一种特殊机制,因为从本质上来说,这是要处理由于 C 的不规范而带来的不规范问题,有因为如果从用重载必然会引用保护主义者的怒吼。当时我是想尽量谨慎和保守,现在我已经认为这是个错误了。按其本来的定义,特化是重载的一种受限的不规则的形式,无法与语言的其他部分很好地配合。
类似用法广泛见于 C++ 经典著作,例如 Scott Meyers 在 Effective C++ [Meyers 2005](及 Effective 系列的其他书)里使用 specialization 就是这个意思。对应完全特化,他使用的既不是 full specialization,也不是 explicit specialization,而是 total specialization。又如最著名的 C++ 教科书之一,C++ Primer [Lippman et al. 2013],里面在用到 specialization 或 template specialization 时,也同时包含了完全特化和部分特化。
向标准靠拢
不过,在 C++ 之父后面的作品里,术语已经发生了变化,他对上面的“特化”用法改用了“用户定义特化”(user-defined specialization)一词,如 [Stroustrup 2013]:
By default, a template gives a single definition to be used for every template argument (or combination of template arguments) that a user can think of.Many such design concerns can be addressed by providing alternative definitions of the template and having the compiler choose between them based on the template arguments provided where they are used. Such alternative definitions of a template are called user-defined specializations, or simply user specializations.
默认情况下,模板会提供一个单一的通用定义,适用于用户能设想到的所有模板参数(或模板参数组合)。许多此类设计问题可以通过为模板提供替代定义来解决,并让编译器根据使用时的模板参数在这些定义之间进行选择。这样的模板替代定义被称为用户定义特化,简称为用户特化。
你可能会疑惑,那在 C++ 标准里是如何表述这样一种用户特化的概念的呢?
答案是要么使用 program-defined specialization(偶尔用 user-defined specialization),要么写全,如:
program-defined specialization
〈library〉 explicit template specialization or partial specialization that is not part of the C++ standard library and not defined by the implementationA class declaration where the class-name in the class-head-name is a simple-template-id shall be an explicit specialization (13.9.4) or a partial specialization (13.7.6).
When the injected-class-name of a class template specialization or partial specialization is used as a type-name, it is equivalent to the template-name followed by the template-arguments of the class template specialization or partial specialization enclosed in
<>
.The behavior of a C++ program is undefined if it declares an explicit or partial specialization of any standard library variable template, except where explicitly permitted by the specification of that variable template.
It is unspecified whether the library defines any full or partial specializations of any of these templates. [唯一一处见到用 full specialization 的文本,说明这个说法也已经渗透到至少某些委员的词汇表里了。]
C++ Templates 里的澄清
标准之外还是有着不少混淆,尤其是 specialization 和 explicit specialization 的含义。这个困境在 C++ Templates: The Complete Guide [Vandevoorde et al. 2017] 里也提到了:
The ability to overload function templates, combined with the partial ordering rules to select the “best” matching function template, allows us to add more specialized templates to a generic implementation to tune code transparently for greater efficiency. However, class templates and variable templates cannot be overloaded. Instead, another mechanism was chosen to enable transparent customization of class templates: explicit specialization. The standard term explicit specialization refers to a language feature that we call full specialization instead. It provides an implementation for a template with template parameters that are fully substituted: No template parameters remain. Class templates, function templates, and variable templates can be fully specialized.
利用对函数模板进行重载的功能,结合用于选择“最佳”匹配函数模板的部分排序规则,使我们能够向泛型实现中添加更“特化”的模板,从而透明地优化代码以获得更高效率。然而,类模板和变量模板不能被重载。为此,C++ 选择了另一种机制来实现类模板的透明定制:显式特化。标准术语中的显式特化实际上对应我们称为完全特化的语言特性。完全特化为模板提供一个所有模板参数均被完全替换的实现:不再保留任何模板参数。类模板、函数模板和变量模板均可进行完全特化。In a later section, we will describe partial specialization. This is similar to full specialization, but instead of fully substituting the template parameters, some parameterization is left in the alternative implementation of a template. Full specializations and partial specializations are both equally “explicit” in our source code, which is why we avoid the term explicit specialization in our discussion.
在后续章节中,我们将讨论部分特化。这种机制与完全特化类似,但关键区别在于:部分特化不会完全替换所有模板参数,而是在模板的替代实现中保留部分参数化能力。完全特化与部分特化在源代码中都同样是“显式”的——这正是我们在讨论中避免使用标准术语显式特化(explicit specialization)的原因。
这些概念确实让人有点头晕,事实上也真让我在写《C++ 实战:核心技术与最佳实践》时犯迷糊了:在我发现标准里使用的 specialization 一词的意思跟日常不同时,我不正确地使用了“显式特化”一词来表示“用户定义特化”,而没有意识到“显式特化”在标准里并不是字面上的意思。
总结
最后,我用一张图来总结一下。
也就是说,按照标准术语,一个特化必定不是一个模板,而是一个具体的函数、类或变量;部分特化仍是模板,虽然能归到用户定义特化里,但却不是一种 C++ 标准里描述的特化。再换种角度,部分特化是尚未完成的(incomplete)特化,因此不是特化。
最后的建议:
- 当你写下没有修饰的“特化”时,明确说明你的意思是标准里的特化(不包含部分特化)还是用户定义特化(包含部分特化)。
- 考虑跟 C++ Templates 一样,避免“显式特化”这一可能引发误解的术语。
本文算是对这个问题的一个小结了吧。
(为统一翻译、一致表述起见,本文中对引用段落的翻译没有使用官方中文版的译文,但仍可能部分参考了官方译文的说法。)
参考文献
[Lippman et al. 2013] Stanley et al., C++ Primer, 5ed. Addison-Wesley, 2013.
中文版:《C++ Primer(中文版第 5 版)》(王刚、杨巨峰译,电子工业出版社)。
[N4950] Working Draft, Standard for Programming Language C. https://open-std.org/JTC1/SC22/WG21/docs/papers/2023/n4950.pdf.
[Meyers 2005] Scott Meyers, Effective C++: 55 Specific Ways to Improve Your Programs and Designs, 3ed. Addison-Wesley, 2005.
[Stroustrup 1994] Bjarne Stroustrup, The Design and Evolution of C++. Addison-Wesley, 1994.
中文版:《C++ 语言的设计和演化》(裘宗燕译,机械工业出版社)。
[Stroustrup 2013] Bjarne Stroustrup, The C++ Programming Language, 4ed. Addison-Wesley, 2013.
[Vandevoorde et al. 2017] David Vandevoorde et al., C++ Templates: The Complete Guide, 2ed. Addison-Wesley, 2017.
中文版:《C++ Templates(第 2 版)中文版》(何荣华、王文斌、张毅峰、杨文波译,人民邮电出版社 )。