Thread overview | |||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
April 19, 2010 templates | ||||
---|---|---|---|---|
| ||||
Hello. Say I have a [struct] template T, which takes a param S. Any T!(S) satisfies a certain template constraint W, so I can use any T!(S) the same way. I want to be able to store heterogeneous T!(S) in a single list. Is there any good way to express the type for this? |
April 19, 2010 Re: templates | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ellery Newcomer Attachments:
| On Mon, Apr 19, 2010 at 20:16, Ellery Newcomer <ellery-newcomer@utulsa.edu>wrote: > Hello. > > Say I have a [struct] template T, which takes a param S. > > Any T!(S) satisfies a certain template constraint W, so I can use any T!(S) > the same way. I want to be able to store heterogeneous T!(S) in a single > list. Is there any good way to express the type for this? > So, you have struct S(T) {} and W!(S!T)) is true, whatever T is. Right? S!T is a type by itself, different from S!U, S!V, ... So I'm afraid there is no direct way to store them in an array. S by itself is not a type nor a struct, it's code waiting to be instantiated. Maybe you could use Variant to hide the inner type away, and do an array of S!Variant. But then, there is no easy way to get back the type inside the variant. Another solution is to use a TypeTuple or a Tuple: import std.traits; struct SList(Ss...) if (allSatisfy!(W, Ss)) { Ss theList; } I'm using W as a predicate here. Philippe |
April 19, 2010 Re: templates | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ellery Newcomer | Ellery Newcomer: > Say I have a [struct] template T, which takes a param S. > > Any T!(S) satisfies a certain template constraint W, so I can use any T!(S) the same way. I want to be able to store heterogeneous T!(S) in a single list. Is there any good way to express the type for this? In D the templates with their arguments create a Nominative type system, so they are all seen as distinct types: http://en.wikipedia.org/wiki/Nominative_type_system So in D the standard, simpler and safer way to create a (flat) hierarchy of things that can be mixed in a container is to use the same strategy you use in Java, to create a base class/interface/abstract class, create a container of such base thing, and then populate it. If you don't want objects, and you want templated structs, then you have to work more, and your code will probably be less safe. In your situation you can create a common struct, it can contain several common fields, or just a tag, this is the minimal: enum FooTags { Tag1, Tag2, ... } struct BaseFoo { FooTags tag; // few common attributes // few common methods } (That BaseFoo can also be a template mixin, then you can mix in this template at the top of all your derived structs.) struct Foo1 { FooTags tag = FooTags.Tag1; ... } Then you have to read the tag and cast the pointer to the pointer of the correct type, in a switch if necessary... If your structs are allocated from the C heap, and you have only up to 8 or 16 different structs, you can even use a tagged pointer, but you also need an aligned malloc wrapper. This saves the 1 or or 2 or 4 bytes for the tag. Bye, bearophile |
April 19, 2010 Re: templates | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ellery Newcomer | On Mon, 19 Apr 2010 14:16:03 -0400, Ellery Newcomer <ellery-newcomer@utulsa.edu> wrote:
> Hello.
>
> Say I have a [struct] template T, which takes a param S.
>
> Any T!(S) satisfies a certain template constraint W, so I can use any T!(S) the same way. I want to be able to store heterogeneous T!(S) in a single list. Is there any good way to express the type for this?
What you are looking for is a conversion from compile-time interface to runtime interface. The only drawback is, you can't go backwards (from a runtime interface to a compile-time).
This can be possible in runtime reflection systems, but the theory is that RTTI can be built from compile-time type info.
Here is a quick-and-dirty solution, if you don't mind using classes/interfaces. You are going to need some sort of runtime interface in order to get this to work, classes/interfaces are not the leanest way to do this, but it should get the job done:
The S is an extra complication that can be factored out, so let's forget about S for now. Let's assume W defines a single function int foo(int). Let's make a W interface:
interface IW
{
int foo(int);
}
Now, we can define a class template to hold your values:
class WByVal(T) if (implementsW!T)
{
this(T val) {this._t = val;}
private T _t;
int foo(int x)
{
return _t.foo(x);
}
}
Maybe a helper function
WByVal!(T) makeW(T)(T _t)
{
return new WByVal!(T)(_t);
}
Now you can easily convert a T to a IW with makeW, and by definition, any IW implements the W functions, so it can be used in anything that accepts W by constraint. So you can now form lists/arrays of IW objects to be used as needed.
One other possibility is to use a union, but I think Variant might be better at that point.
-Steve
|
April 19, 2010 Re: templates | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On Mon, 19 Apr 2010 15:16:46 -0400, Steven Schveighoffer <schveiguy@yahoo.com> wrote:
> On Mon, 19 Apr 2010 14:16:03 -0400, Ellery Newcomer <ellery-newcomer@utulsa.edu> wrote:
>
>> Hello.
>>
>> Say I have a [struct] template T, which takes a param S.
>>
>> Any T!(S) satisfies a certain template constraint W, so I can use any T!(S) the same way. I want to be able to store heterogeneous T!(S) in a single list. Is there any good way to express the type for this?
>
> What you are looking for is a conversion from compile-time interface to runtime interface. The only drawback is, you can't go backwards (from a runtime interface to a compile-time).
>
> This can be possible in runtime reflection systems, but the theory is that RTTI can be built from compile-time type info.
>
> Here is a quick-and-dirty solution, if you don't mind using classes/interfaces. You are going to need some sort of runtime interface in order to get this to work, classes/interfaces are not the leanest way to do this, but it should get the job done:
>
> The S is an extra complication that can be factored out, so let's forget about S for now. Let's assume W defines a single function int foo(int). Let's make a W interface:
>
> interface IW
> {
> int foo(int);
> }
>
> Now, we can define a class template to hold your values:
>
> class WByVal(T) if (implementsW!T)
Whoops! Forgot the interface!
class WByVal(T) : IW if (implementsW!T)
-Steve
|
April 19, 2010 Re: templates | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ellery Newcomer | Ellery Newcomer wrote: > I want to be able to store heterogeneous T!(S) in a single list std.boxer or std.variant may be useful: http://digitalmars.com/d/2.0/phobos/std_boxer.html http://digitalmars.com/d/2.0/phobos/std_variant.html Ali |
April 19, 2010 Re: templates | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer Attachments:
| On Mon, Apr 19, 2010 at 21:20, Steven Schveighoffer <schveiguy@yahoo.com>wrote:
>
> Here is a quick-and-dirty solution, if you don't mind using
>> classes/interfaces.
>>
> (snip)
I'm not used to using interfaces in this way. What become the stored T values when you cast the classes into IW to construct your array? I suppose they're lost?
Does that mean that if we have a bunch of T (all different types),you map a
call to makeW on it, get back a bunch of WByVal!T (all different types),
cast them to IW and... what? erase the types?
Philippe
|
April 19, 2010 Re: templates | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ali Çehreli Attachments:
| On Mon, Apr 19, 2010 at 21:52, Ali Çehreli <acehreli@yahoo.com> wrote:
> Ellery Newcomer wrote:
>
>> I want to be able to store heterogeneous T!(S) in a single list
>>
>
> std.boxer or std.variant may be useful:
>
> http://digitalmars.com/d/2.0/phobos/std_boxer.html
>
> http://digitalmars.com/d/2.0/phobos/std_variant.html
>
> Ali
>
But how do you get back what was stored inside a Variant without any
indication as to what it was initially?
As far as I get it, Variant is useful to have a variable that can be
assigned with many other variables of different types during its lifetime.
I also regularly want to use it to store _one_ thing, anything, to get it back later. But then I'm at a loss as to how crack open the Variant afterwards, without storing some sort of type information somewhere.
|
April 19, 2010 Re: templates | ||||
---|---|---|---|---|
| ||||
Posted in reply to Philippe Sigaud | On Mon, 19 Apr 2010 16:52:40 -0400, Philippe Sigaud <philippe.sigaud@gmail.com> wrote: > On Mon, Apr 19, 2010 at 21:20, Steven Schveighoffer <schveiguy@yahoo.com>wrote: > >> >> Here is a quick-and-dirty solution, if you don't mind using >>> classes/interfaces. >>> >> (snip) > > I'm not used to using interfaces in this way. What become the stored T > values when you cast the classes into IW to construct your array? I suppose > they're lost? Not sure what you mean... > > Does that mean that if we have a bunch of T (all different types),you map a > call to makeW on it, get back a bunch of WByVal!T (all different types), > cast them to IW and... what? erase the types? What I had in mind was: struct S { int foo(int x) { return x * 2; } } struct S2 { int foo(int x) { return x + 2; } } void main() { S s1; S2 s2; IW[] ws; ws ~= makeW(s1); ws ~= makeW(s2); assert(ws[0].foo(3) == 6); assert(ws[1].foo(3) == 5); } does this help? -Steve |
April 19, 2010 Re: templates | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | Steven Schveighoffer:
> What I had in mind was:
>
> struct S
> {
> int foo(int x) { return x * 2; }
> }
>
> struct S2
> {
> int foo(int x) { return x + 2; }
> }
>
> void main()
> {
> S s1;
> S2 s2;
>
> IW[] ws;
>
> ws ~= makeW(s1);
> ws ~= makeW(s2);
>
> assert(ws[0].foo(3) == 6);
> assert(ws[1].foo(3) == 5);
> }
>
> does this help?
A possible solution, this code is just an idea that needs improvements, it's not generic:
struct S1 {
int foo(int x) { return x * 2; }
}
struct S2 {
int foo(int x) { return x + 2; }
}
enum TypeTag { TS1, TS2 }
struct Wrapper {
TypeTag tag;
union {
S1 s1;
S2 s2;
}
int foo(int x) {
final switch(this.tag) {
case TypeTag.TS1: return s1.foo(x);
case TypeTag.TS2: return s2.foo(x);
}
}
}
Wrapper makeW(T)(T s) if (is(T == S1) || is(T == S2)) {
static if (is(T == S1)) {
Wrapper result = Wrapper(TypeTag.TS1);
result.s1 = s;
return result;
} else static if (is(T == S2)) {
Wrapper result = Wrapper(TypeTag.TS2);
result.s2 = s;
return result;
} else
assert(0);
}
void main() {
S1 s1;
S2 s2;
Wrapper[] ws;
ws ~= makeW(s1);
ws ~= makeW(s2);
assert(ws[0].foo(3) == 6);
assert(ws[1].foo(3) == 5);
}
There are several other ways to solve this problem. This version is a bit nicer because it contains no pointer casts.
Languages that have a tagged union (like Cyclone) need quite less code here, but probably D2 can be used to remove some of that code duplication.
Bye,
bearophile
|
Copyright © 1999-2021 by the D Language Foundation