Cached at:
07/21/26, 06:39 PM
# (Ab)using Overload Sets to Create Ad-Hoc Template APIs
Source: [https://blog.dlang.org/2026/07/19/abusing-overload-sets-to-create-ad-hoc-template-apis/](https://blog.dlang.org/2026/07/19/abusing-overload-sets-to-create-ad-hoc-template-apis/)
“Overload set” is a term of art that seemingly arises from the reuse of overload rules across the years\. Overload sets quietly power a lot of D’s generic programming, and once you understand them explicitly, a whole class of API design opens up\.
```
import std;
void bar(int) { "int".writeln; }
void bar(float) { "float".writeln; }
void bar(bool) { "bool".writeln; }
void useIt(alias F)() {
F(1); // prints "int"
F(13.37); // prints "float"
F(true); // prints "bool"
}
unittest { useIt!bar; }
```
What exactly is`bar`when passed to`useIt`? Wait, does it*survive*as`useIt\.F`? \(Yes, it compiles as is\.\)
## What Are Overload Sets?
[The specification](https://dlang.org/spec/function.html#overload-sets):
> An Overload Set is the set of functions with the same name declared in the same scope that participate in overload resolution\.
And from[the alias spec](https://dlang.org/spec/declaration.html#alias-overload):
> Aliases can also ‘import’ a set of overloaded functions that can be overloaded with functions in the current scope…
The behavior of overload sets is scattered across the spec\. A few sentences here, a few more tucked away in the template section\. Template specialization lets you pattern\-match against the whole set; pick the right implementation based on compile\-time arguments\.
Overload sets can work with several very different things\. I use the following definition:
> An overload set is a collection of things that share a name and a closely related template header\.
You may not know everything that can be templatized, and real\-world template usage is extremely concerned about making only one declaration match at a time\.
## Specialization on Integer Values
```
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){ // returns the pseudo-length of an overloadset
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);
}
// prints: int, float, string
```
By treating`value`specialization of`int`s as an array, you can make an overload set that is foreachable\. You can build compile\-time lookup tables, generate code for each type in the set, and dispatch based on integer constants, etc\.
## Real\-World Pattern: Unified Vector Interface
In[Kap’s joka library](https://github.com/Kapendev/joka/blob/main/source/joka/math.d),`GVec2`,`GVec3`,`GVec4`are all parameterized by type, but what if I want dimension\-generic code?
It’s an incomplete templatization:
```
// Separate types for each dimension
alias BVec2 = GVec2!byte;
alias IVec2 = GVec2!int;
// ... more aliases
struct GVec2(T) { T x, y; }
struct GVec3(T) { T x, y, z; }
struct GVec4(T) { T x, y, z, w; }
```
You can’t write “works on any N\-dimensional vector”; the dimension is stuck in the type name\. To specialize on dimension, you can expand the shared template header to include an`int`parameter\.
```
alias GVec2(T = float) = GVec!(2, T); // (optional, allows backwards compatibility)
alias GVec3(T = float) = GVec!(3, T);
alias GVec4(T = float) = GVec!(4, T);
// struct GVec(int N, T); // implicit, no primary definition exists
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; }
```
Now you can write functions that take`GVec\!\(N, T\)`and work across any dimension\. Pattern\-matching on`N`at compile time allows you to easily access`N`in trivial meta\-code:
```
void print(int N, T)(GVec!(N, T) v) {
import std;
writeln("Vector of dimension ", N, " with type ", 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;
}
```
The above is an**Ad\-hoc Template API**\. Note that no primary`struct GVec\(int N, T\)`exists in the source code\. The symbol`GVec`exists only as a collection of specializations\. The API is a coordinate map of successful matches\. You are programming against the existence of a match in the resolution logic rather than a central definition\.
```
The API that can be named is not the immortal API. -monkyyy-tzu
```
## Swapping an Overload Set
`std\.conv\.to`is an overload set with the implied syntax of`\.to\!T`\. You can let users pass their own instead for serialization\.
```
template someSerializeFunc(alias TO_ = void, Args...)(Args args) {
static if (is(TO_ == void)) {
import std.conv;
alias TO = std.conv.to; // Successful partial symbol resolution
} else {
alias TO = TO_;
}
// ... use args with .TO!T for conversions
}
```
This code uses the default`std\.conv\.to`unless the user provides their own\. So long as the user matches the ad\-hoc API of`to`, they can extend it \(and fix its edge cases\)\. This flexible API, with an overloadable\-overload\-set, lets users fix behavior\.
```
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;
}
```
The overload set resolution and specialization mechanisms work on all types of templates in D\. The compiler only cares about something matching the template header; you can define it however you want and make ad\-hoc APIs\.
With`type`and`value`specialization shared across at least four declaration patterns, which of your problems could be approached by asking, “How can I define a good template header”?
## Conclusion
Overload sets aren’t exotic\. Combine them with template specialization and you get ad\-hoc APIs that support multiple access patterns without duplicating code\. Build your types to play nice with meta\-programs, design your APIs to take overload sets, and let users swap in their own implementations\. This works even for more complex APIs such as`std\.conv\.to`\.
---
Based on 2 chapters from my book,[‘Black Magic in D’](https://crazymonkyyy.github.io/blackmagic-in-d/)\.
https://crazymonkyyy\.github\.io/