www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Type polymorphism and type variance

reply "js.mdnq" <js_adddot+mdng gmail.com> writes:
One thing I've always struggled with in oop is how to deal with
storing generic types.

A very simple example is, suppose you had to design a way to
store generic types.

class myGtype(T) { }

...

myGType[] gcollection;  // should store various types such as
myGtype!int, myGtype!myobj, etc.., possibly even other things
like myotherobj, etc..

Obviously we can't store different types in a homogenous array.
I know D has the ability to use variant types that basically
overcome this. I imagine it is very inefficient to do it this way?

In my mind, it seems like one could never get around the issue
without storing type information along with the data because the
compiler will eventually need to know the type information to
know how to deal with the data?

e.g.,

auto x = gcollection[i]; // x's type is not determined

auto x = cast(myGtype!int)gcollection[i]; // x's type is forced
to be myGtype!int, but may not be if gcollection is heterogeneous.

If we stored type information along with the data then we could
use it to cast to the correct type and make the compiler happy.
This seems like a rather inefficient way.


Are there any direct oop ways to do this. If, for example, I make
a wrapper class in which I can create a homogenous array of, but
somehow the wrapper class managed the heterogeneous nature of the
types, then it might work. But it seems to me, no matter how one
tries to make it work, it is impossible(at least efficiently).

In my specific case I am wanting to simply store objects in an
array of the type myObject!T where T could be any type so I can
access functions in the type(which unfortunately depend on the
type).

e.g.,

class myObject(T)
{
     void SetValue(T v) { }
}

....


myObject!?[] arr;     // Not possible
arr[0].SetValue(x)    // But what I am wanting to do.

In fact, I'll have some way to always cast x to the correct
type(or at least, if not, throw an error).


My question:

Is what I've discussed above basically doable with plan old OOP
using some design pattern and not some fancy compiler or
"tricks/hacks"? Basically, am I missing something obvious or is
the problem actually difficult and requires special methods(like
std.variant)?
Dec 04 2012
parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 12/04/2012 06:42 PM, js.mdnq wrote:
 One thing I've always struggled with in oop is how to deal with
 storing generic types.

 A very simple example is, suppose you had to design a way to
 store generic types.

 class myGtype(T) { }

 ...

 myGType[] gcollection; // should store various types such as
 myGtype!int, myGtype!myobj, etc.., possibly even other things
 like myotherobj, etc..

 Obviously we can't store different types in a homogenous array.
 I know D has the ability to use variant types that basically
 overcome this. I imagine it is very inefficient to do it this way?

 In my mind, it seems like one could never get around the issue
 without storing type information along with the data because the
 compiler will eventually need to know the type information to
 know how to deal with the data?

 e.g.,

 auto x = gcollection[i]; // x's type is not determined
What do you expect to do with x? Unless there is a common interface the compiler cannot compile the code without knowing the type of x. Note that even myGtype!int and myGtype!double are completely different types with completely different capabilities.
 auto x = cast(myGtype!int)gcollection[i]; // x's type is forced
 to be myGtype!int, but may not be if gcollection is heterogeneous.

 If we stored type information along with the data then we could
 use it to cast to the correct type and make the compiler happy.
Alas, the compiler is not available at runtime.
 Are there any direct oop ways to do this.
The easiest way in OOP would be interfaces and polymorphism. The myGtype class template below implements the MyInterface interface and it enables us to put different types of objects in a collection. import std.stdio; interface MyInterface { void foo(); MyInterface dup(); } class myGtype(T) : MyInterface { void foo() { specialOperationFor!T(); } myGtype dup() { return new myGtype(); } } void specialOperationFor(T : double)() { writefln("Special operation for %s", T.stringof); } struct S { int i; } void specialOperationFor(T : S)() { writefln("Special operation for %s", T.stringof); } void main() { MyInterface[] objects; objects ~= new myGtype!double(); objects ~= new myGtype!int(); objects ~= new myGtype!S(); foreach (o; objects) { o.foo(); } // We can even copy them and the copies have the correct types: foreach (i, o; objects) { auto c = o.dup(); writefln("Using the copy of item %s", i); c.foo(); } } The output: Special operation for double Special operation for int Special operation for S Using the copy of item 0 Special operation for double Using the copy of item 1 Special operation for int Using the copy of item 2 Special operation for S Ali
Dec 04 2012
next sibling parent reply Lubos Pintes <lubos.pintes gmail.com> writes:
Sorry maybe I am stupid but where is the value of concrete T?
Or perhaps I completely misunderstood this?
Dňa 5. 12. 2012 4:59 Ali Çehreli  wrote / napísal(a):
 On 12/04/2012 06:42 PM, js.mdnq wrote:
  > One thing I've always struggled with in oop is how to deal with
  > storing generic types.
  >
  > A very simple example is, suppose you had to design a way to
  > store generic types.
  >
  > class myGtype(T) { }
  >
  > ...
  >
  > myGType[] gcollection; // should store various types such as
  > myGtype!int, myGtype!myobj, etc.., possibly even other things
  > like myotherobj, etc..
  >
  > Obviously we can't store different types in a homogenous array.
  > I know D has the ability to use variant types that basically
  > overcome this. I imagine it is very inefficient to do it this way?
  >
  > In my mind, it seems like one could never get around the issue
  > without storing type information along with the data because the
  > compiler will eventually need to know the type information to
  > know how to deal with the data?
  >
  > e.g.,
  >
  > auto x = gcollection[i]; // x's type is not determined

 What do you expect to do with x? Unless there is a common interface the
 compiler cannot compile the code without knowing the type of x.

 Note that even myGtype!int and myGtype!double are completely different
 types with completely different capabilities.

  > auto x = cast(myGtype!int)gcollection[i]; // x's type is forced
  > to be myGtype!int, but may not be if gcollection is heterogeneous.
  >
  > If we stored type information along with the data then we could
  > use it to cast to the correct type and make the compiler happy.

 Alas, the compiler is not available at runtime.

  > Are there any direct oop ways to do this.

 The easiest way in OOP would be interfaces and polymorphism. The myGtype
 class template below implements the MyInterface interface and it enables
 us to put different types of objects in a collection.

 import std.stdio;

 interface MyInterface
 {
      void foo();
      MyInterface dup();
 }

 class myGtype(T) : MyInterface
 {
      void foo()
      {
           specialOperationFor!T();
      }

      myGtype dup()
      {
          return new myGtype();
      }
 }

 void specialOperationFor(T : double)()
 {
      writefln("Special operation for %s", T.stringof);
 }

 struct S
 {
      int i;
 }

 void specialOperationFor(T : S)()
 {
      writefln("Special operation for %s", T.stringof);
 }

 void main()
 {
      MyInterface[] objects;

      objects ~= new myGtype!double();
      objects ~= new myGtype!int();
      objects ~= new myGtype!S();

      foreach (o; objects) {
          o.foo();
      }

      // We can even copy them and the copies have the correct types:
      foreach (i, o; objects) {
          auto c = o.dup();
          writefln("Using the copy of item %s", i);
          c.foo();
      }
 }

 The output:

 Special operation for double
 Special operation for int
 Special operation for S
 Using the copy of item 0
 Special operation for double
 Using the copy of item 1
 Special operation for int
 Using the copy of item 2
 Special operation for S

 Ali
Dec 05 2012
parent =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 12/05/2012 04:28 AM, Lubos Pintes wrote:

 where is the value of concrete T?
 Or perhaps I completely misunderstood this?
Maybe I misunderstand you. :) Are you pointing out that the class template that I have shown did not have any member variables? If so, they can have anything they want. I have changed the program to include different members and even specialization (for dchar): import std.stdio; /* This defines what I want to do with my objects: */ interface MyInterface { void foo(); /* This is just a silly function to demonstrate that copying an object * must be the responsibility of that object because we do not know the * concerete type at this level. */ MyInterface dup(); } /* A class template that implements the interface */ class myGtype(T) if (!is(T == dchar)) : MyInterface { /* Here is a concrete value: */ T value; /* The implementation can be arbitrarily complex for different T: */ static if (is (T == S)) { S[string] aa; } this(T value) { this.value = value; static if (is (T == S)) { aa["hello"] = value; } } void foo() { /* This demonstrates how operations that depend on T can be used * inside this class template. * * specialOperationFor() could alternatively be a member function * template but I find it simpler when it is a free-standing function. */ specialOperationFor!T(value); } /* This one simply uses 'value'. It could do anything else. */ myGtype dup() { return new myGtype(value); } } /* It is possible to implement a specialization of the type completely * separately: */ class myGtype(T) if (is(T == dchar)) : MyInterface { void foo() { writeln("Inside myGtype!dchar.foo"); } myGtype dup() { return new myGtype(); } } void specialOperationFor(T : double)(double d) { writefln("Special operation for double: %s", d); } struct S { int i; } void specialOperationFor(T : S)(S s) { writefln("Special operation for S: %s", s); } void main() { MyInterface[] objects; objects ~= new myGtype!double(1.5); objects ~= new myGtype!int(42); objects ~= new myGtype!S(S(100)); objects ~= new myGtype!dchar(); foreach (o; objects) { o.foo(); } // We can even copy them and the copies have the correct types: foreach (i, o; objects) { auto c = o.dup(); writefln("Using the copy of item %s", i); c.foo(); } } Ali
Dec 05 2012
prev sibling parent reply "js.mdnq" <js_adddot+mdng gmail.com> writes:
On Wednesday, 5 December 2012 at 03:59:29 UTC, Ali Çehreli wrote:
 On 12/04/2012 06:42 PM, js.mdnq wrote:
 One thing I've always struggled with in oop is how to deal
with
 storing generic types.

 A very simple example is, suppose you had to design a way to
 store generic types.

 class myGtype(T) { }

 ...

 myGType[] gcollection; // should store various types such as
 myGtype!int, myGtype!myobj, etc.., possibly even other things
 like myotherobj, etc..

 Obviously we can't store different types in a homogenous
array.
 I know D has the ability to use variant types that basically
 overcome this. I imagine it is very inefficient to do it this
way?
 In my mind, it seems like one could never get around the issue
 without storing type information along with the data because
the
 compiler will eventually need to know the type information to
 know how to deal with the data?

 e.g.,

 auto x = gcollection[i]; // x's type is not determined
What do you expect to do with x? Unless there is a common interface the compiler cannot compile the code without knowing the type of x. Note that even myGtype!int and myGtype!double are completely different types with completely different capabilities.
 auto x = cast(myGtype!int)gcollection[i]; // x's type is
forced
 to be myGtype!int, but may not be if gcollection is
heterogeneous.
 If we stored type information along with the data then we
could
 use it to cast to the correct type and make the compiler
happy. Alas, the compiler is not available at runtime.
 Are there any direct oop ways to do this.
The easiest way in OOP would be interfaces and polymorphism. The myGtype class template below implements the MyInterface interface and it enables us to put different types of objects in a collection. import std.stdio; interface MyInterface { void foo(); MyInterface dup(); } class myGtype(T) : MyInterface { void foo() { specialOperationFor!T(); } myGtype dup() { return new myGtype(); } } void specialOperationFor(T : double)() { writefln("Special operation for %s", T.stringof); } struct S { int i; } void specialOperationFor(T : S)() { writefln("Special operation for %s", T.stringof); } void main() { MyInterface[] objects; objects ~= new myGtype!double(); objects ~= new myGtype!int(); objects ~= new myGtype!S(); foreach (o; objects) { o.foo(); } // We can even copy them and the copies have the correct types: foreach (i, o; objects) { auto c = o.dup(); writefln("Using the copy of item %s", i); c.foo(); } } The output: Special operation for double Special operation for int Special operation for S Using the copy of item 0 Special operation for double Using the copy of item 1 Special operation for int Using the copy of item 2 Special operation for S Ali
[Sorry about all the dups, it seems many times lately the forum is not accepting my posts, seems like it went through this time even though it said it didn't] Yeah, I basically understand that Ali, I wrote another post that updated what I was doing that was more pertinent. Your method is akin to just storing the objects as object types but you end up wrapping them in a type to encapsulate the template. You did answer my question but unfortunately I basically asked the wrong question(or at least too simplified). --------------------- I am trying to create a direct acyclic graph where nodes are objects and edges are functions between the nodes. R[T in (T1, T2,...)][F] nodes; so nodes[somenode] is an array of objects of types that depend on the specific nodes. nodes[somenode][1] will be, say, a function from somenode to nodes[somenode][1]. These functions will take the nodes, which can be of different types, and compute some value on them. R will be a function ptr that takes two nodes. (but it is variant) e.g., myNode!A n1; myNode!B n2; bool function(myNode!A, myNode!B) compn1n2; compn1n2(n1,n2); is the basic idea. But every pair of connected nodes will possibly have a different function associated with it. While I can use a common interface of the nodes and even that of the edges I don't see how I can pass around data between the two without resorting to passing objects and storing type information. If this is possible maybe you can extend your example class myGtype(T) : MyInterface { T Value; // <-------- added void foo() { specialOperationFor!T(); } myGtype dup() { return new myGtype(); } } ... foreach (o; objects) { o.foo(); } but instead I need to do something like auto value = o[0].foo(o[1]); where value is then some computation between o[0] and o[1]. (in general I'll have to random objects of a common interface stored in my array that I want to do a computation on using a function associated with them) Maybe all that is needed is another level in encapsulation to hide away the template parameters sort of like what you did? (and it will need to be done not only on the nodes but the edges to be able to store them too) Thanks for the help. (if your having trouble understanding the problem then just think of how you would efficiently store the nodes and edges of a directed acyclic graph. Each node is of a somewhat arbitrary type and edges are functions over the nodes that do computations, possibly changing the nodes "value". These all need to be stored someway so that a full computation chain can be carried out(the graph structure is arbitrary).)
Dec 05 2012
parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 12/05/2012 09:51 AM, js.mdnq wrote:

 (if your having trouble understanding the problem then just think of how
 you would efficiently store the nodes and edges of a directed acyclic
 graph. Each node is of a somewhat arbitrary type and edges are functions
 over the nodes that do computations, possibly changing the nodes
 "value". These all need to be stored someway so that a full computation
 chain can be carried out(the graph structure is arbitrary).)
I do not fully understand the issue yet but I smell "double dispatch" in there. There is no elegant solution of double dispatch at least in C++ but there are a number of solutions with certain compromises. I will read your post more carefully later. Ali
Dec 05 2012
parent reply "js.mdnq" <js_adddot+mdng gmail.com> writes:
On Wednesday, 5 December 2012 at 22:53:11 UTC, Ali Çehreli wrote:
 On 12/05/2012 09:51 AM, js.mdnq wrote:

 (if your having trouble understanding the problem then just
think of how
 you would efficiently store the nodes and edges of a directed
acyclic
 graph. Each node is of a somewhat arbitrary type and edges
are functions
 over the nodes that do computations, possibly changing the
nodes
 "value". These all need to be stored someway so that a full
computation
 chain can be carried out(the graph structure is arbitrary).)
I do not fully understand the issue yet but I smell "double dispatch" in there. There is no elegant solution of double dispatch at least in C++ but there are a number of solutions with certain compromises. I will read your post more carefully later. Ali
Thanks, I'll look at yours too when my head is more clear. I did have a better post but it didn't get through. Here is possibly a better overview: http://faculty.ucr.edu/~hanneman/nettext/Figure7_11.jpg is a good representation of what I'm trying to do. The way I'm thinking about it is that for each node there is a function that takes the data from all the other nodes that are coming to it. So, A's function is nill, B's function takes 2 values A.Data and D.Data, C's function takes B.Data and C.Data, D's function takes B.Data and E.Data, and E's function takes B.Data. Each function is different as it will not necessarily be the same computation and the data in each node can be of a different type. This is easy to do by just passing around an array of objects and their types then using a switch to typecast. Each "function" know exactly what data it's trying to use though unless the "user" made a mistake as they will supply the function they want to use. The edge direction also specifies the flow the computations as I will iterate through the graph computing values on nodes. In my case I will expect that a "cycle" of computations will be nilpotent. This keeps any sequence of computations from creating unstable data that does stuff like "toggles" back and forth or jumping around after each computation. For example, If A.Data is an int, B.Data is a bool, and D.Data is a double then the user will supply a function that takes an int and a double that returns a bool. There just has to be a better way that avoids casting everything to an object and using the very slow variant type. ;/ The issue is mainly about storing the objects because this is very easy to do statically. Thanks again.
Dec 05 2012
parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 12/05/2012 04:40 PM, js.mdnq wrote:
 On Wednesday, 5 December 2012 at 22:53:11 UTC, Ali Çehreli wrote:
 On 12/05/2012 09:51 AM, js.mdnq wrote:

 (if your having trouble understanding the problem then just
think of how
 you would efficiently store the nodes and edges of a directed
acyclic
 graph. Each node is of a somewhat arbitrary type and edges
are functions
 over the nodes that do computations, possibly changing the
nodes
 "value". These all need to be stored someway so that a full
computation
 chain can be carried out(the graph structure is arbitrary).)
I do not fully understand the issue yet but I smell "double dispatch" in there. There is no elegant solution of double dispatch at least in C++ but there are a number of solutions with certain compromises. I will read your post more carefully later. Ali
Thanks, I'll look at yours too when my head is more clear. I did have a better post but it didn't get through. Here is possibly a better overview: http://faculty.ucr.edu/~hanneman/nettext/Figure7_11.jpg is a good representation of what I'm trying to do. The way I'm thinking about it is that for each node there is a function that takes the data from all the other nodes that are coming to it. So, A's function is nill, B's function takes 2 values A.Data and D.Data, C's function takes B.Data and C.Data, D's function takes B.Data and E.Data, and E's function takes B.Data. Each function is different as it will not necessarily be the same computation and the data in each node can be of a different type. This is easy to do by just passing around an array of objects and their types then using a switch to typecast. Each "function" know exactly what data it's trying to use though unless the "user" made a mistake as they will supply the function they want to use. The edge direction also specifies the flow the computations as I will iterate through the graph computing values on nodes. In my case I will expect that a "cycle" of computations will be nilpotent. This keeps any sequence of computations from creating unstable data that does stuff like "toggles" back and forth or jumping around after each computation. For example, If A.Data is an int, B.Data is a bool, and D.Data is a double then the user will supply a function that takes an int and a double that returns a bool. There just has to be a better way that avoids casting everything to an object and using the very slow variant type. ;/ The issue is mainly about storing the objects because this is very easy to do statically. Thanks again.
Here is a solution that stores the types of the points in delegates. That is the "static information" that we know at compile time: import std.stdio; /* Although this can be a polymorphic type, I am assuming that all of the edge * handlers will produce the same type of result. */ struct HandlingResult {} /* This is the edge handler for (int, double) as well as (double, int). (See * below.) */ HandlingResult edgeHandler(int i, double d) { writefln("Handling %s and %s", i, d); return HandlingResult(); } struct S { int i; } /* This is the edge handler for (int, S) and (S, int). */ HandlingResult edgeHandler(int i, S s) { writefln("Handling %s and %s", i, s); return HandlingResult(); } /* This defines what gets done with edges. */ interface IEdge { HandlingResult handle(); } class Edge(T0, T1) : IEdge { alias HandlingResult function(T0, T1) Handler; /* Each edge consists of two values and a handling function. */ T0 value0; T1 value1; Handler func; this(T0 value0, T1 value1, Handler func) { this.value0 = value0; this.value1 = value1; this.func = func; } /* Simply dispatches to the corresponding function. */ HandlingResult handle() { return func(value0, value1); } } /* I've imagined that the function that handles int,double would handle * double,int as well. This template tries the arguments both ways. * * Note 1: This is just a draft. It should probably also detect ambiguities. * * Note 2: I had to use delegates as I think there is no way of taking the * address of a specific overload of a function. */ template HandlerFuncSelector(T0, T1) { static if (__traits(compiles, edgeHandler(T0.init, T1.init))) { enum result = (T0 a, T1 b) => edgeHandler(a, b); } else static if (__traits(compiles, edgeHandler(T1.init, T0.init))) { enum result = (T0 a, T1 b) => edgeHandler(b, a); } else { static assert(false, "No edgeHandler(" ~ T0.stringof ~ ", " ~ T1.stringof ~ ") defined"); } } /* The convenience function to make an Edge after choosing its handler. */ Edge!(T0, T1) edge(T0, T1)(T0 value0, T1 value1) { return new Edge!(T0, T1)(value0, value1, HandlerFuncSelector!(T0, T1).result); } void main() { IEdge[] edges; /* These will be handled by edgeHandler(int,double). */ edges ~= edge(1, 1.5); edges ~= edge(2.5, 2); /* These will be handled by edgeHandler(int,S). */ edges ~= edge(3, S(100)); edges ~= edge(S(200), 4); foreach (edge; edges) { edge.handle(); } } Ali
Dec 05 2012
parent reply "js.mdnq" <js_adddot+mdng gmail.com> writes:
On Thursday, 6 December 2012 at 03:22:55 UTC, Ali Çehreli wrote:
 On 12/05/2012 04:40 PM, js.mdnq wrote:
 On Wednesday, 5 December 2012 at 22:53:11 UTC, Ali Çehreli
wrote:
 On 12/05/2012 09:51 AM, js.mdnq wrote:

 (if your having trouble understanding the problem then just
think of how
 you would efficiently store the nodes and edges of a
directed
 acyclic
 graph. Each node is of a somewhat arbitrary type and edges
are functions
 over the nodes that do computations, possibly changing the
nodes
 "value". These all need to be stored someway so that a full
computation
 chain can be carried out(the graph structure is
arbitrary).)
 I do not fully understand the issue yet but I smell "double
dispatch"
 in there. There is no elegant solution of double dispatch at
least in
 C++ but there are a number of solutions with certain
compromises. I
 will read your post more carefully later.

 Ali
Thanks, I'll look at yours too when my head is more clear. I
did have a
 better post but it didn't get through.

 Here is possibly a better overview:

 http://faculty.ucr.edu/~hanneman/nettext/Figure7_11.jpg

 is a good representation of what I'm trying to do.

 The way I'm thinking about it is that for each node there is
a function
 that takes the data from all the other nodes that are coming
to it.
 So, A's function is nill, B's function takes 2 values A.Data
and D.Data,
 C's function takes B.Data and C.Data, D's function takes
B.Data and
 E.Data, and E's function takes B.Data.

 Each function is different as it will not necessarily be the
same
 computation and the data in each node can be of a different
type.
 This is easy to do by just passing around an array of objects
and their
 types then using a switch to typecast. Each "function" know
exactly what
 data it's trying to use though unless the "user" made a
mistake as they
 will supply the function they want to use.

 The edge direction also specifies the flow the computations
as I will
 iterate through the graph computing values on nodes. In my
case I will
 expect that a "cycle" of computations will be nilpotent. This
keeps any
 sequence of computations from creating unstable data that
does stuff
 like "toggles" back and forth or jumping around after each
computation.
 For example,

 If A.Data is an int, B.Data is a bool, and D.Data is a double
then the
 user will supply a function that takes an int and a double
that returns
 a bool.


 There just has to be a better way that avoids casting
everything to an
 object and using the very slow variant type. ;/ The issue is
mainly
 about storing the objects because this is very easy to do
statically.
 Thanks again.
Here is a solution that stores the types of the points in delegates. That is the "static information" that we know at compile time: import std.stdio; /* Although this can be a polymorphic type, I am assuming that all of the edge * handlers will produce the same type of result. */ struct HandlingResult {} /* This is the edge handler for (int, double) as well as (double, int). (See * below.) */ HandlingResult edgeHandler(int i, double d) { writefln("Handling %s and %s", i, d); return HandlingResult(); } struct S { int i; } /* This is the edge handler for (int, S) and (S, int). */ HandlingResult edgeHandler(int i, S s) { writefln("Handling %s and %s", i, s); return HandlingResult(); } /* This defines what gets done with edges. */ interface IEdge { HandlingResult handle(); } class Edge(T0, T1) : IEdge { alias HandlingResult function(T0, T1) Handler; /* Each edge consists of two values and a handling function. */ T0 value0; T1 value1; Handler func; this(T0 value0, T1 value1, Handler func) { this.value0 = value0; this.value1 = value1; this.func = func; } /* Simply dispatches to the corresponding function. */ HandlingResult handle() { return func(value0, value1); } } /* I've imagined that the function that handles int,double would handle * double,int as well. This template tries the arguments both ways. * * Note 1: This is just a draft. It should probably also detect ambiguities. * * Note 2: I had to use delegates as I think there is no way of taking the * address of a specific overload of a function. */ template HandlerFuncSelector(T0, T1) { static if (__traits(compiles, edgeHandler(T0.init, T1.init))) { enum result = (T0 a, T1 b) => edgeHandler(a, b); } else static if (__traits(compiles, edgeHandler(T1.init, T0.init))) { enum result = (T0 a, T1 b) => edgeHandler(b, a); } else { static assert(false, "No edgeHandler(" ~ T0.stringof ~ ", " ~ T1.stringof ~ ") defined"); } } /* The convenience function to make an Edge after choosing its handler. */ Edge!(T0, T1) edge(T0, T1)(T0 value0, T1 value1) { return new Edge!(T0, T1)(value0, value1, HandlerFuncSelector!(T0, T1).result); } void main() { IEdge[] edges; /* These will be handled by edgeHandler(int,double). */ edges ~= edge(1, 1.5); edges ~= edge(2.5, 2); /* These will be handled by edgeHandler(int,S). */ edges ~= edge(3, S(100)); edges ~= edge(S(200), 4); foreach (edge; edges) { edge.handle(); } } Ali
Ok, I see I can use a class to wrap a generic function and then store the class. For some unknown I didn't think it was possible. When doing it though, there seems to be a ton of overhead. Remember though, the return type of the functions have to match the node type they are associated with. For example, if B is a node and B.Edge if the "function" that maps other nodes's values to B's value then B.Edge must have the same type as B.Value. essentially B.Value = B.Edge(A, C, Q) // or whatever I'll study your code as there seems to be a lot of "goodies" in it that I need to learn. Thanks
Dec 06 2012
parent =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 12/06/2012 12:08 PM, js.mdnq wrote:
 On Thursday, 6 December 2012 at 03:22:55 UTC, Ali Çehreli wrote:
The following comment is relevant:
 /* Although this can be a polymorphic type, I am assuming that all of
 the edge
 * handlers will produce the same type of result. */
 struct HandlingResult
 {}
 Remember though, the return type of the functions have to match the node
 type they are associated with.
If HandlingResult is an interface, then the Edge functions can all return a descendent of that interface. (In OOP speak, edgeHandler() functions would have "covariant return types".) Ali
Dec 06 2012