June 21, 2019

foo(cast(void*)[Object1, Object2]);

foo(cast(bool delegate(void*))(Tuple!(X,Y) objects)) { });

One can pass arbitrary data as an array(the cast is ugly though)

Then the tuple is cast back to void* but one can access objects correctly with typing.

This works well when interacting with C/C++ void* data passing but can also work with passing heterogeneous objects in general.




import std.stdio, std.typecons;

class X { int x = 1; }
class Y { int y = 2; }

// a ** is required for the cast.
void foo(void** data)
{
    auto dd = cast(Tuple!(X,Y)*)(data);
    auto d = *dd;
   writeln(d[0].x);
   writeln(d[1].y);
}


// This would be a C callback that is hidden from us but the callback isn't.
alias Q = void delegate(void*);
void bar(Q d, void* x)
{
    d(x);
}



void main()
{
    X x = new X;
    Y y = new Y;
    foo(cast(void**)[x, y]);
    bar(cast(Q)(Tuple!(X,Y)* dd)
        {
            auto d = *dd;
            writeln("> ", d[0].x);
            writeln("> ", d[1].y);
        }, cast(void*)[x, y]);
}


What would be nice is if one didn't actually have to jump through all the hoops:


void foo(void* as Tuple!(X,Y) d) // Redundant here but only because we have access to foo
{
   writeln(d[0].x);
   writeln(d[1].y);
}

// This would be a C callback that is hidden from us but the callback isn't.
alias Q = void delegate(void*);
void bar(Q d, void* x)
{
    d(x);
}



void main()
{
    X x = new X;
    Y y = new Y;
    foo([x, y]);
    bar((as Tuple!(X,Y) d)
        {
            writeln("> ", d[0].x);
            writeln("> ", d[1].y);
        }, as [x, y]);
}


here I'm using as to signify a sort of implicit cast.

The main thing to note is that for the callback, D gets the type as it is defined  even though the cast changes the overall delegate to what it should be:


bar(cast(Q)(Tuple!(X,Y)* dd)
   {
        auto d = *dd;
        writeln("> ", d[0].x);
        writeln("> ", d[1].y);
    }, cast(void*)[x, y]);
}


I don't actually use * in my code I think it is necessary to get the right values due to how they are passed. I think it has to do with register vs stack passing. So it might be fragile

What I have is

bar(cast(Q)(Tuple!(X,Y) d)
   {
        writeln("> ", d[0].x);
        writeln("> ", d[1].y);
    }, cast(void*)[x, y]);
}

which works, at least for x64.

The cool thing is the "casting" all happens internally and so there is no direct need for casting. Obviously though there is no real casting going on(one could swap x and y in the argument) and so it is dangerous... but D could make it 100% type safe with the appropriate syntax. (only for delegates and callbacks) although maybe one could use it for normal functions too:

foo([x,y] as Tuple!(X,Y))

e.g., normally we would have to do

bar((dd)
   {
        auto d = cast(Tuple!(X,Y))dd; // Doesn't work though because tuple is not a reference, so must use the double pointer hacks.
        writeln("> ", d[0].x);
        writeln("> ", d[1].y);
    }, cast(void*)[x, y]);
}


When working with quite a number of C callbacks and passing data, this is a nice pattern to use to avoid the ugly temp variables and casting.