March 13, 2021
On Saturday, 13 March 2021 at 00:36:37 UTC, David  Zhang wrote:
> On Friday, 12 March 2021 at 22:18:59 UTC, tsbockman wrote:
>> You can use TypeInfo references as the keys for the struct types, too. It's better than hashes because the TypeInfo is already being generated anyway, and is guaranteed to be unique, unlike your hashes which could theoretically collide.
>
> Makes sense. Using TypeInfo never occurred to me. I assume they are generated for COM classes as well?

I'm not sure about that; you should test it yourself. I know that runtime type information support is incomplete for some non-`extern(D)` types - for example:
    https://issues.dlang.org/show_bug.cgi?id=21690
You can always fall back to fully qualified names for non-`extern(D)` stuff if you have to.

But you should protect against hash collisions somehow, if you go that route. Here's a hybrid approach that is immune to hash collisions and works across DLL boundaries, but can almost always verify equality with a single pointer comparison:

///////////////////////////////////////
struct TypeKey {
private:
    const(void)* ptr;
    size_t length;

    this(const(TypeInfo) typeInfo) const pure @trusted nothrow @nogc {
        ptr = cast(const(void)*) typeInfo;
        length = 0u;
    }
    this(string fqn) immutable pure @trusted nothrow {
        /* We need to allocate a block of size_t to ensure proper alignment
        for the first chunk, which is the hash code of the fqn string: */
        size_t[] chunks = new size_t[1 + (fqn.length + (size_t.sizeof - 1)) / size_t.sizeof];
        chunks[0] = hashOf(fqn);
        (cast(char*) chunks.ptr)[size_t.sizeof .. size_t.sizeof + fqn.length] = fqn;

        ptr = cast(immutable(void)*) chunks.ptr;
        length = fqn.length;
    }

    @property const(TypeInfo) typeInfo() const pure @trusted nothrow @nogc
        in(length == 0u)
    {
        return cast(const(TypeInfo)) ptr;
    }
    @property size_t fqnHash() const pure @trusted nothrow @nogc
        in(length != 0u)
    {
        return *cast(const(size_t)*) ptr;
    }
    @property string fqn() const pure @trusted nothrow @nogc
        in(length != 0u)
    {
        const fqnPtr = cast(immutable(char)*) (this.ptr + size_t.sizeof);
        return fqnPtr[0 .. length];
    }

public:
    string toString() const @safe {
        return (length == 0u)? typeInfo.toString() : fqn; }
    size_t toHash() const @safe nothrow {
        return (length == 0u)? typeInfo.toHash : fqnHash; }
    bool opEquals(TypeKey that) const @trusted {
        if(this.ptr is that.ptr)
            return true;
        if(this.length != that.length)
            return false;
        if(length == 0u)
            return (this.typeInfo == that.typeInfo);
        if(this.fqnHash != that.fqnHash)
            return false;
        return (this.fqn == that.fqn);
    }
}
template typeKeyOf(Indirect)
    if(is(Indirect : T*, T) // Support structs, static arrays, etc.
    || is(Indirect == interface) || is(Indirect == class))
{
    static if(is(Indirect : T*, T) || (__traits(getLinkage, Indirect) == "D")) {
        @property const(TypeKey) typeKeyOf() pure @safe nothrow @nogc {
            return const(TypeKey)(typeid(Indirect)); }
    } else {
        /* For FQN-based keys, ideally the whole process should share a single
        copy of the heap allocated fqn and its hash, so we'll use a global. No
        synchronization is necessary post construction, since it is immutable: */
        private immutable TypeKey masterKey;
        shared static this() {
            // With some bit-twiddling, this could be done at compile time, if needed:
            import std.traits : fullyQualifiedName;
            masterKey = immutable(TypeKey)(fullyQualifiedName!Indirect);
        }
        @property immutable(TypeKey) typeKeyOf() @safe nothrow @nogc {
            assert(masterKey.ptr !is null);
            return masterKey;
        }
    }
}
@safe unittest {
    static extern(C++) class X { }
    static extern(C++) class Y { }

    assert(typeKeyOf!(int*) == typeKeyOf!(int*));
    assert(typeKeyOf!(int*) != typeKeyOf!(float*));
    assert(typeKeyOf!X == typeKeyOf!X);
    assert(typeKeyOf!X != typeKeyOf!Y);
    assert(typeKeyOf!X != typeKeyOf!(int*));
    assert(typeKeyOf!(float*) != typeKeyOf!Y);
}
///////////////////////////////////////

>> The lowering is something like this:
>> ...
> Makes sense, I always thought of them as existing in separate places.

Yeah, there are multiple reasonable ways of supporting interfaces in a language, each with their own trade-offs. But, this is the one used by D at the moment.

> So much head-bashing, and it's over. Thanks, tsbockman, Imperatorn.

You're very welcome!
March 13, 2021
On Friday, 12 March 2021 at 17:37:43 UTC, David  Zhang wrote:
> I want to store interfaces as untyped void[], then cast them back to the interface at a later time. However, it appears to produce garbage values on get().
>
> Is this even possible, and if so, what is happening here? The alternative would be a struct { CheckedPtr self; api_fns.... }
>
> e.g.
>
> void register(I)(I i) {
>   auto mem = new void[](I.sizeof);
>   memcpy(mem.ptr, cast(void*) i, I.sizeof);
>
>   // CheckedPtr includes a hash of fullyQualifiedName
>   map[i.get_name()] = CheckedPtr!I(mem.ptr);
> }
>
> I get(I)() {
>   // basically cast(I) p
>   return map[I.get_name()].as!I();
> }

Maybe I don't get this right but why you don't just use the void[] as it is?

void[] mem = [i];
//...
return (cast(T[]) mem)[0];

This is how I exchange variable data between DLLs.

March 13, 2021
On Saturday, 13 March 2021 at 15:44:53 UTC, frame wrote:
> Maybe I don't get this right but why you don't just use the void[] as it is?
>
> void[] mem = [i];
> //...
> return (cast(T[]) mem)[0];
>
> This is how I exchange variable data between DLLs.

That works, and is more elegant than the OP's solution. (And, I didn't know you could do it that way, so thanks for sharing!)

However, it still adds an unnecessary second level of indirection for interfaces and classes. The simplest and most efficient solution is actually this:

    auto indirect = cast(void*) i; // i is of type I.
    // ...
    return cast(I) indirect;

Where `is(I : T*, T) || is(I == interface) || is(I == class)`. The constraint enforces that all types should be accessed through exactly one level of indirection, allowing the same code to handle both reference types and value types with maximum efficiency.

1 2
Next ›   Last »