Something like ADL from C++?

Bradley Chatha sealabjaster at gmail.com
Tue Dec 3 15:31:43 UTC 2024


On Tuesday, 3 December 2024 at 11:55:50 UTC, Manu wrote:
> Has anyone ever experimented with a pattern like this? 
> Essentially, I can't work out how to expand /combine an 
> overload set to include symbols from multiple imports....

I gave this go using an eponymous template - it's definitely a 
bit yucky, but seems to do as you want (would obviously need more 
extensive work for real use cases rather than this simple POC):

## serialise.d

```d
module serialise;

import std.traits : isNumeric;

string serialise(T)(T foo)
if(isNumeric!T)
{
     import std.conv : to;
     return foo.to!string();
}

string serialise(string s)
{
     return s;
}

string doSerialise(Things...)(Things things)
{
     string slowBuilder;

     static foreach (i, Thing; Things)
     {{
         enum hasSerialiser = __traits(compiles, 
serialiserFor!Thing);
         static if(hasSerialiser)
             slowBuilder ~= serialiserFor!Thing(things[i]);
         else
             slowBuilder ~= serialise(things[i]);
     }}

     return slowBuilder;
}

template serialiserFor(Thing)
{
     import std.traits : fullyQualifiedName;
     mixin("import thingMod = 
"~fullyQualifiedName!(__traits(parent, Thing))~";");
     alias serialiserFor = thingMod.serialise;
}
```

## app.d

```d
module app;

import std.stdio;

void main()
{
	import serialise;
	import std : writeln;

	writeln(doSerialise(UserThing("a")));
	writeln(doSerialise("a"));
}

struct UserThing
{
	string a;
}

string serialise(UserThing thing)
{
	return "UserThing: " ~ thing.a;
}
```

## Result

```
UserThing: a
a
```

Namely:

The use of an eponymous template helps avoid the symbol 
resolution issue - you could also try to mixin a local import 
like `import userthing : userThingSerialise = serialise` instead, 
but a template might be cleaner.

I use a `__traits(compiles)` ~~abuse~~ check since it's simple, 
but you can probably also do some stuff with 
[__traits(hasMember)](https://dlang.org/spec/traits.html#hasMember), `__traits(getMember)`, etc.

Another potential way if keeping the serialise function separate 
from the actual type is mandatory, which I can't be bothered to 
prototype, is to make a template struct similar to this:

```d
struct Serialiser(UserThingT, alias SerialiserFunc){...}
```

And then have a specific overload within the main serialiser 
module to handle this case:

```d
// Something like this at least.
yada serialise(Thing)(...)
if(isInstanceOf!(Serialiser, Thing))
{
   // Use SerialiserFunc against Thing?
}
```

Though there's probably a bunch of issues with lifetimes, const, 
ref, etc. with that approach.

Definitely an interesting issue, though personally I'd try my 
best to allow types to have a `serialise` function directly be a 
part of them rather than using free standing extentions.


More information about the Digitalmars-d mailing list