(滥用)重载集在D语言中创建临时模板API
摘要
一篇详细的博客文章,解释了如何利用D语言的重载集创建临时模板API,涵盖了针对整数值的特化以及用于维度通用向量接口的真实世界模式。
暂无内容
查看缓存全文
缓存时间: 2026/07/21 18:39
# (滥用)重载集创建临时模板API
来源:https://blog.dlang.org/2026/07/19/abusing-overload-sets-to-create-ad-hoc-template-apis/
“重载集”是一个术语,似乎源于多年来对重载规则的复用。重载集低调地驱动着D语言大量泛型编程,一旦你明确理解了它们,一整套API设计思路就会打开。
```
import std;
void bar(int) { "int".writeln; }
void bar(float) { "float".writeln; }
void bar(bool) { "bool".writeln; }
void useIt(alias F)() {
F(1); // 打印 "int"
F(13.37); // 打印 "float"
F(true); // 打印 "bool"
}
unittest { useIt!bar; }
```
当`bar`被传递给`useIt`时,它到底是什么?等等,它作为`useIt.F`会**存活**下来吗?(是的,它就这样编译通过。)
## 什么是重载集?
规范文档(https://dlang.org/spec/function.html#overload-sets)指出:
> 重载集是在同一作用域中声明的、参与重载解析的一组同名函数。
此外,别名规范(https://dlang.org/spec/declaration.html#alias-overload)提到:
> 别名也可以“导入”一组重载函数,并且可以与当前作用域中的函数重载……
重载集的行为分散在规范的不同地方——这里几句话,那里又藏了几段。模板特化允许你对整个集合进行模式匹配;基于编译时参数选择正确的实现。
重载集可以与多种截然不同的东西配合使用。我采用如下定义:
> 重载集是一组共享名称和紧密相关的模板头的集合。
你可能不知道有多少东西可以被模板化,而实际项目中的模板使用非常注重每次只让一个声明匹配。
## 基于整数值的特化
```
alias typeAt(int I : 0) = int;
alias typeAt(int I : 1) = float;
alias typeAt(int I : 2) = string;
enum typeAtLength = Length!typeAt;
template Length(alias A, int acc=0){ // 返回重载集的伪长度
static if( ! __traits(compiles,A!acc) ){
enum Length=acc;
} else {
enum Length=Length!(A,acc+1);
}
}
static foreach (i; 0 .. typeAtLength) {
pragma(msg, typeAt!i.stringof);
}
// 打印: int, float, string
```
通过将`int`的`值`特化视为数组,你可以创建一个可foreach的重载集。你可以构建编译时查找表、为集合中的每个类型生成代码、基于整数常量进行分发等。
## 现实世界模式:统一向量接口
在 Kap 的 joka 库(https://github.com/Kapendev/joka/blob/main/source/joka/math.d)中,`GVec2`、`GVec3`、`GVec4` 都是参数化类型,但如果我想要维度通用的代码呢?
这是一个不完整的模板化:
```
// 每个维度有独立的类型
alias BVec2 = GVec2!byte;
alias IVec2 = GVec2!int;
// ... 更多别名
struct GVec2(T) { T x, y; }
struct GVec3(T) { T x, y, z; }
struct GVec4(T) { T x, y, z, w; }
```
你不能写“适用于任意 N 维向量”;维度被锁定在类型名中。为了基于维度进行特化,你可以将共享的模板头扩展,包含一个 `int` 参数。
```
alias GVec2(T = float) = GVec!(2, T); // (可选,允许向后兼容)
alias GVec3(T = float) = GVec!(3, T);
alias GVec4(T = float) = GVec!(4, T);
// struct GVec(int N, T); // 隐式声明,不存在主定义
struct GVec(int N : 2, T) { T x = 0; T y = 0; }
struct GVec(int N : 3, T) { T x = 0; T y = 0; T z = 0; }
struct GVec(int N : 4, T) { T x = 0; T y = 0; T z = 0; T w = 0; }
```
现在你可以编写接受 `GVec!(N, T)` 并在任意维度上工作的函数。在编译时对 `N` 进行模式匹配,让你能轻松地在简单的元代码中访问 `N`:
```
void print(int N, T)(GVec!(N, T) v) {
import std;
writeln("维度为 ", N, " 的向量,类型为 ", T.stringof);
static foreach (c; "xyzw"[0 .. N]) {
writeln(c, ": ", mixin("v." ~ c));
}
}
unittest {
IVec3(1, 4, 7).print;
DVec4(2, 6, 0, 0).print;
}
```
以上就是一个**临时模板 API**。注意源代码中并不存在主模板 `struct GVec(int N, T)`。符号 `GVec` 仅作为一系列特化的集合存在。该 API 是一个成功匹配的坐标映射。你是在针对解析逻辑中是否存在匹配进行编程,而不是针对一个中心定义。
```
可以被命名的 API 不是不朽的 API。 —— monkyyy-tzu
```
## 替换重载集
`std.conv.to` 是一个重载集,隐含了 `.to!T` 的语法。你可以让用户传递他们自己的转换函数以用于序列化。
```
template someSerializeFunc(alias TO_ = void, Args...)(Args args) {
static if (is(TO_ == void)) {
import std.conv;
alias TO = std.conv.to; // 成功的部分符号解析
} else {
alias TO = TO_;
}
// ... 使用 args 配合 .TO!T 进行转换
}
```
这段代码默认使用 `std.conv.to`,除非用户提供了自己的。只要用户匹配了 `to` 的临时 API,他们就可以扩展它(并修复其边界情况)。这种灵活的 API,结合可重载的重载集,允许用户修正行为。
```
enum foo(int I : 0) = 0;
int foo(int I : 1)(int) => 1;
template foo(int I : 2) {
int foo = 2;
}
struct foo(int I : 3) {
int myint = 3;
}
```
重载集解析和特化机制适用于 D 语言中所有类型的模板。编译器只关心某个东西是否匹配模板头;你可以以任何方式定义它,并创建临时 API。
通过至少四种声明模式共享的 `类型` 和 `值` 特化,你的哪些问题可以通过问“如何定义一个良好的模板头”来解决?
## 结论
重载集并非常见。将它们与模板特化结合起来,你就得到了支持多种访问模式且无需重复代码的临时 API。构建与元程序良好协作的类型,设计接受重载集的 API,并让用户能够替换为自己的实现。这甚至适用于像 `std.conv.to` 这样更复杂的 API。
---
基于我的书《Black Magic in D》中的两章(https://crazymonkyyy.github.io/blackmagic-in-d/)。
https://crazymonkyyy\.github\.io/
相似文章
Data types à la carte (2008)
本文提出了一种从独立组件组合数据类型和函数的技术,并将该方法扩展到结合自由单子,从而实现了对Haskell的IO单子的模块化结构。
从组合混乱到线性优雅:构建转换引擎架构
Minimal 的一篇博文,详细介绍了他们如何使用中间表示(Intermediate Representation)来线性管理复杂性,构建文件格式转换引擎,并与生物学的蝴蝶结架构进行了类比。
优化CPU密集型Go热路径的笔记
本文讨论了CPU密集型Go代码的性能优化技术,指出了泛型和接口抽象因无法内联而产生的局限性,并主张在热路径中使用代码复制。文章通过一个Brotli移植示例和深入基准测试进行了说明。
@doodlestein: https://x.com/doodlestein/status/2073825011249418358
来自AI模型Claude Fable在Rust中设计一个全面的计算几何与物理模拟框架时的详细推理过程,其中融入了共形几何代数和层上同调等高级数学概念。
对 APL 等数组语言的有原则性重新思考
本文提出了一种有原则性的方法来重新思考 APL 等数组语言,通过将变量建模为输入维度的函数,旨在相较于传统方法提高可读性和错误检查能力。