Making one struct work in place of another for function calls.

cc cc at nevernet.com
Wed Apr 17 15:45:28 UTC 2024


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.


More information about the Digitalmars-d-learn mailing list