www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Making one struct work in place of another for function calls.

reply Liam McGillivray <yoshi.pit.link.mario gmail.com> writes:
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)?
Apr 16
parent reply Paul Backus <snarwin gmail.com> writes:
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][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`. [1]: https://en.wikipedia.org/wiki/Row_polymorphism
Apr 16
parent reply Liam McGillivray <yoshi.pit.link.mario gmail.com> writes:
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.
Apr 16
next sibling parent Andy Valencia <dont spam.me> writes:
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(); }
Apr 16
prev sibling parent cc <cc nevernet.com> writes:
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.
Apr 17