Jump to page: 1 2
Thread overview
templates
Apr 19, 2010
Ellery Newcomer
Apr 19, 2010
Philippe Sigaud
Apr 19, 2010
bearophile
Apr 19, 2010
Philippe Sigaud
Apr 19, 2010
bearophile
Apr 19, 2010
Ellery Newcomer
Apr 19, 2010
bearophile
Apr 20, 2010
bearophile
Apr 20, 2010
Philippe Sigaud
Apr 19, 2010
Ellery Newcomer
Apr 19, 2010
Ali Çehreli
Apr 19, 2010
Philippe Sigaud
Apr 20, 2010
BLS
April 19, 2010
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
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
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
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
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
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
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
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
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
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
« First   ‹ Prev
1 2