On Wednesday, 17 April 2024 at 03:13:46 UTC, Liam McGillivray wrote:
On Wednesday, 17 April 2024 at 02:39:25 UTC, Paul Backus wrote:
This is called [row polymorphism][1], and it does not exist in D.

You could approximate it by making `someFunction` a template, and accepting any type `T` that has the necessary members instead of only accepting `typeB`. But this is only possible if you are free to modify the definition of `someFunction`.

Is there a way I can replace "`TypeB`" in the function parameters with another symbol, and then define that symbol to accept `TypeB` as an argument, but also accept `TypeA` which would get converted to `TypeB` using a function? I'm willing to make a function template if it's rather simple.

Normal template approach would be as simple as:
```d
struct TypeA {
        int x, y;
        string name;
}
struct TypeB {
        float x, y;
        ubyte[] data;
}
// Strict version, only allows the named structs
float someFunction(S)(S input) if (is(S == TypeA) || is(S == TypeB)) {
        writefln("input loc: %s,%s", input.x, input.y);
        return 0;
}
void main() {
        someFunction(TypeA(1,1));
        someFunction(TypeB(2,2));
}
```

Or you could write the function like:
```d
// Permission version, accepts any struct with the required members float someFunction(S)(S input) if (is(S == struct) && isNumeric!(typeof(S.x)) && isNumeric!(typeof(S.y))) {
        writefln("input loc: %s,%s", input.x, input.y);
        return 0;
}
```

In fact, you don't even necessarily need the template constraints:
```d
// It just works... usually
float someFunction(S)(S input) {
        writefln("input loc: %s,%s", input.x, input.y);
        return 0;
}
```
But then you might get (at best) less clear error messages when a wrong type is passed, and (at worst) funny business if someone passes a type that technically satisfies the function behavior but isn't actually a type you expected and is treating those members differently. It's often ideal to have either some type of template constraints, or static ifs/static asserts in the function body so you know what you're dealing with.

Another solution, without templates, if you can for instance modify TypeB but not TypeA, is to give TypeB a constructor that takes a TypeA as an argument.
```d
struct TypeA {
        int x, y;
        string name;
}
struct TypeB {
        float x, y;
        immutable(ubyte)[] data;
        this(float x, float y) { // We need a constructor here now too
                this.x = x;
                this.y = y;
        }
        this(TypeA a) {
                this.x = a.x;
                this.y = a.y;
                this.data = cast(immutable(ubyte)[]) a.name;
        }
}
float someFunction(TypeB input) {
        writefln("input loc: %s,%s", input.x, input.y);
        return 0;
}
auto someFunction(TypeA input) => someFunction(TypeB(input));
```
If you cannot modify either struct definition, you could do the conversion by hand in the stub instead.

Additionally, if you have many functions and you don't want to write stubs for all of them, you could use mixins to generate them for you like so:
```d
float someFunctionUno(TypeB input) {
        writefln("uno loc: %s,%s", input.x, input.y);
        return 0;
}
float someFunctionDos(TypeB input) {
        writefln("dos loc: %s,%s", input.x, input.y);
        return 0;
}
float someFunctionTres(TypeB input) {
        writefln("tres loc: %s,%s", input.x, input.y);
        return 0;
}
// Consider also UDAs, iterating member functions, etc
static foreach (sym; "someFunctionUno someFunctionDos someFunctionTres".split) { mixin(format(`auto %s(TypeA input) => %s(TypeB(input));`, sym, sym));
}
void main() {
        someFunctionUno(TypeA(1,1));
        someFunctionDos(TypeB(2,2));
}
```

There are yet other ways to do it as well. The solution you'll use will depend on more detailed specifics. In many cases, when working with similar data types and you want the function itself to be as agnostic as possible about what it's dealing with, templates are often the way to go. D's templating/metaprogramming capacities are extremely powerful and flexible. However, it may not necessarily be the right choice if you need to rely on very specific handling of the data types in question and their layouts.

Reply via email to