digitalmars.D.learn - Type polymorphism and type variance
- js.mdnq (48/48) Dec 04 2012 One thing I've always struggled with in oop is how to deal with
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (65/88) Dec 04 2012 What do you expect to do with x? Unless there is a common interface the
- Lubos Pintes (3/100) Dec 05 2012 Sorry maybe I am stupid but where is the value of concrete T?
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (96/98) Dec 05 2012 Maybe I misunderstand you. :) Are you pointing out that the class
- js.mdnq (66/174) Dec 05 2012 [Sorry about all the dups, it seems many times lately the forum
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (6/12) Dec 05 2012 I do not fully understand the issue yet but I smell "double dispatch" in...
- js.mdnq (33/51) Dec 05 2012 Thanks, I'll look at yours too when my head is more clear. I did
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (90/140) Dec 05 2012 Here is a solution that stores the types of the points in delegates.
- js.mdnq (13/205) Dec 06 2012 Ok, I see I can use a class to wrap a generic function and then
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (6/14) Dec 06 2012 If HandlingResult is an interface, then the Edge functions can all
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
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 determinedWhat 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
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
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
On Wednesday, 5 December 2012 at 03:59:29 UTC, Ali Çehreli wrote:On 12/04/2012 06:42 PM, js.mdnq wrote:[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).)One thing I've always struggled with in oop is how to dealwithstoring 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 homogenousarray.I know D has the ability to use variant types that basically overcome this. I imagine it is very inefficient to do it thisway?In my mind, it seems like one could never get around the issue without storing type information along with the data becausethecompiler 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 determinedWhat 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 isforcedto be myGtype!int, but may not be if gcollection isheterogeneous.If we stored type information along with the data then wecoulduse it to cast to the correct type and make the compilerhappy. 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
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
On Wednesday, 5 December 2012 at 22:53:11 UTC, Ali Çehreli wrote:On 12/05/2012 09:51 AM, js.mdnq wrote: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.(if your having trouble understanding the problem then justthink of howyou would efficiently store the nodes and edges of a directedacyclicgraph. Each node is of a somewhat arbitrary type and edgesare functionsover the nodes that do computations, possibly changing thenodes"value". These all need to be stored someway so that a fullcomputationchain 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
On 12/05/2012 04:40 PM, js.mdnq wrote:On Wednesday, 5 December 2012 at 22:53:11 UTC, Ali Çehreli wrote: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(); } } AliOn 12/05/2012 09:51 AM, js.mdnq wrote: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.(if your having trouble understanding the problem then justthink of howyou would efficiently store the nodes and edges of a directedacyclicgraph. Each node is of a somewhat arbitrary type and edgesare functionsover the nodes that do computations, possibly changing thenodes"value". These all need to be stored someway so that a fullcomputationchain 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
On Thursday, 6 December 2012 at 03:22:55 UTC, Ali Çehreli wrote:On 12/05/2012 04:40 PM, js.mdnq wrote: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. ThanksOn Wednesday, 5 December 2012 at 22:53:11 UTC, Ali Çehreliwrote:directedOn 12/05/2012 09:51 AM, js.mdnq wrote:(if your having trouble understanding the problem then justthink of howyou would efficiently store the nodes and edges of aarbitrary).)acyclicgraph. Each node is of a somewhat arbitrary type and edgesare functionsover the nodes that do computations, possibly changing thenodes"value". These all need to be stored someway so that a fullcomputationchain can be carried out(the graph structure isdispatch"I do not fully understand the issue yet but I smell "doubleleast inin there. There is no elegant solution of double dispatch atcompromises. IC++ but there are a number of solutions with certaindid have awill read your post more carefully later. AliThanks, I'll look at yours too when my head is more clear. Ibetter 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 isa functionthat takes the data from all the other nodes that are comingto it.So, A's function is nill, B's function takes 2 values A.Dataand D.Data,C's function takes B.Data and C.Data, D's function takesB.Data andE.Data, and E's function takes B.Data. Each function is different as it will not necessarily be thesamecomputation and the data in each node can be of a differenttype.This is easy to do by just passing around an array of objectsand theirtypes then using a switch to typecast. Each "function" knowexactly whatdata it's trying to use though unless the "user" made amistake as theywill supply the function they want to use. The edge direction also specifies the flow the computationsas I williterate through the graph computing values on nodes. In mycase I willexpect that a "cycle" of computations will be nilpotent. Thiskeeps anysequence of computations from creating unstable data thatdoes stufflike "toggles" back and forth or jumping around after eachcomputation.For example, If A.Data is an int, B.Data is a bool, and D.Data is a doublethen theuser will supply a function that takes an int and a doublethat returnsa bool. There just has to be a better way that avoids castingeverything to anobject and using the very slow variant type. ;/ The issue ismainlyabout storing the objects because this is very easy to dostatically.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 06 2012
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