Search code examples
arraysooppointersreferenced

Class Array in D


Can I create a class array in D? Something like:

interface A {}
class AA: A {}
class AB: A {}
class AC: A {}

ClassList!A list = new ClassList!A {AA, AB, AC};

void testf(ulong testv) {
    A a = new list[testv];
}

Solution

  • Yes, that's possible, though not necessarily exactly how you have it there. You can make a list of types with a type tuple:

    import std.typetuple;
    alias list = TypeTuple!(AA, AB, AC);
    

    But, you can't index it like an array at runtime; trying new list[n] will be a compile time error. Instead, you'll create a helper function that loops over it and returns the instance, like this:

    foreach(index, type; list)
        if(index == testv) {
            a = new type();
            break;
        }
    

    The way that compiles is foreach(foo; compile_time_list) actually becomes a big unrolled loop. The generated code is as if you wrote:

    if(0 == testv) { a = new AA(); goto end; }
    if(1 == testv) { a = new AB(); goto end; }
    if(2 == testv) { a = new AC(); goto end; }
    end:
    

    So that works with runtime values, bridging the gap between the compile time list and the array index you want.

    It's worth noting that this isn't necessarily the most efficient, but unless your class list has like a thousand entries, i doubt that would matter. Another option for more speed is to generate a switch statement at compile time from your list, then mix that in. The switch statement will compile to a more efficient lookup table. But odds are the simple loop is fine.

    Anyway, putting it together, we get:

    import std.stdio;
    
    interface A {}
    class AA: A {}
    class AB: A {}
    class AC: A {}
    
    import std.typetuple;
    alias list = TypeTuple!(AA, AB, AC);
    
    void testf(ulong testv) {
      A a;
      foreach(index, type; list)
        if(index == testv) {
        a = new type();
            break;
    }
    
      if(a is null)
          writeln("bad type index");
      else {
          writeln(typeid(cast(Object) a), " was created");
      }
    }
    
    void main() {
        testf(0);
        testf(1);
        testf(2);
        testf(3);
    }
    

    Result:

    $ ./test50
    test50.AA was created
    test50.AB was created
    test50.AC was created
    bad type index
    

    just what we wanted.

    BTW, the typeid(cast(Object) a) might look weird, that's fetching the dynamic class of the type. It has to cast to Object first because otherwise, it would print the interface name. Since D interfaces aren't necessarily D classes (they can also be COM objects or C++ classes), the typeid isn't always available. A cast to Object ensures that it is a D class and thus grabs the run time type details.

    EDIT: I saw you asked on the newsgroup as well for checking the base class in the loop. Here's how to do that:

    You could write your own tuple template for it, or just let the compile fail on the factory function: the A a = new T(); will fail if A isn't a base class or interface of T.

    Putting the check in the list could look like this:

    bool checkClassList(Base, T...)() {
       foreach(t; T) {
         static if(!is(t : Base))
             static assert(0, t.stringof ~ " is not a child of " ~ Base.stringof);
       }
      return true;
    }
    
    template ClassList(Base, T...) if(checkClassList!(Base, T)) {
        alias ClassList = T;
    }
    

    Usage:

    alias list = ClassList!(A, AA, AB, AC); // good
    

    add:

    class B {}
    alias list = ClassList!(A, AA, AB, AC, B);
    

    and get error:

    test50.d(12): Error: static assert  "B is not a child of A"
    test50.d(19):        instantiated from here: checkClassList!(A, AA, AB, AC, B)