Thread overview
Making one struct work in place of another for function calls.
Apr 17
cc
April 17

I have two structs that serve roughly the same purpose, and I would like one to be accepted when the other is declared as a function parameter.

To better understand what I mean, take the following example, where I have a function, and two structs.

struct typeA {
    // Some member variables here
}

struct typeB {
    // Some similar member variables here, but in a different format
}

float someFunction(typeB input) {
    // Does some stuff
    // Returns result
}

If I want to be able to call someFunction (or any function with TypeB as a parameter) using TypeA in place of TypeB, and I'm willing to modify the definition of TypeA, I know that I can add an opCast and alias this = opCast!TypeB to TypeA.

But what if typeA is in an external library? Is there any way I can get someFunction (and any function with a typeB parameter) to accept typeA, only modifying the definition of TypeB (and possibly adding a single global line in it's module)?

April 17

On Wednesday, 17 April 2024 at 01:36:59 UTC, Liam McGillivray wrote:

>

To better understand what I mean, take the following example, where I have a function, and two structs.

struct typeA {
    // Some member variables here
}

struct typeB {
    // Some similar member variables here, but in a different format
}

float someFunction(typeB input) {
    // Does some stuff
    // Returns result
}

If I want to be able to call someFunction (or any function with TypeB as a parameter) using TypeA in place of TypeB, and I'm willing to modify the definition of TypeA, I know that I can add an opCast and alias this = opCast!TypeB to TypeA.

But what if typeA is in an external library? Is there any way I can get someFunction (and any function with a typeB parameter) to accept typeA, only modifying the definition of TypeB (and possibly adding a single global line in it's module)?

This is called row polymorphism, 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.

April 17

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.

April 17
On Wednesday, 17 April 2024 at 03:13:46 UTC, Liam McGillivray wrote:
> 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.

Of course, if these were classes, this is classic inheritance and polymorphism.  It would be trivial to subclass the library's version and still have it accepted by things which knew how to use the parent class.  Or the library specified an interface, you could again use the polymorphism.

The closest I got was using function overloads, attached.

Andy

import std.stdio : writeln;

struct Foo {
    int x;

    void doit() {
        writeln(this.x);
    }
}

struct Bar {
    int y;

    // No doit()
}

void
myop(Foo f) {
    f.doit();
}

void
myop(Bar b) {
    auto f = Foo(b.y);
    f.doit();
}

void
main()
{
    auto b = Bar(3);
    b.myop();
}
April 17

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:

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:

// 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:

// 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.

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:

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.