On Friday, 12 March 2021 at 19:24:17 UTC, David  Zhang wrote:
On Friday, 12 March 2021 at 18:50:26 UTC, tsbockman wrote:
<snip>

The idea is to implement a service locator s.t. code like this is possible:
...
I don't really need to copy or move the class instances here, just be able to read, assign, and replace references to them in the same place that I read, assign, and replace void[]'s of structs.

Why do you think you need a `void[]` slice? I think `void*` pointers are sufficient. This handles all normal data types, as long as they are allocated on the GC heap:

////////////////////////////////
/* NOTE: It might be wiser to make this a `final class`
instead of a `struct` to get reference semantics: */
struct Registry {
    private void*[TypeInfo] map;

    /// Return true if the registry was updated, false if not.
    bool put(bool overwrite = true, Indirect)(Indirect i) @trusted
if(is(Indirect : T*, T) // Support structs, static arrays, etc.
        || is(Indirect == interface) || is(Indirect == class))
    {
        auto key = typeid(Indirect), value = cast(void*) i;
        bool updated;
        if(value is null)
            updated = map.remove(key);
        else {
            static if(overwrite) {
                updated = true;
                map[key] = value;
            } else
                updated = (map.require(key, value) is value);
        }
        return updated;
    }
    alias put(Indirect) = put!(true, Indirect);

    bool remove(Indirect)() @trusted
if(is(Indirect : T*, T) // Support structs, static arrays, etc.
        || is(Indirect == interface) || is(Indirect == class))
    {
        return map.remove(typeid(Indirect));
    }

    /** Returns a reference (for interfaces and classes) or
a pointer (for everything else) if the type has been registered,
    and null otherwise. **/
    Indirect get(Indirect)() @trusted
if(is(Indirect : T*, T) // Support structs, static arrays, etc.
        || is(Indirect == interface) || is(Indirect == class))
    {
        return cast(Indirect) map.get(typeid(Indirect), null);
    }
}
@safe unittest {
    static interface I {
        char c() const pure @safe nothrow @nogc;
    }
    static class C : I {
        private char _c;
        this(char c) inout pure @safe nothrow @nogc {
            this._c = c; }
        override char c() const pure @safe nothrow @nogc {
            return _c; }
    }
    static struct S {
        char c = 'S';
        this(char c) inout pure @safe nothrow @nogc {
            this.c = c; }
    }

    Registry registry;
    assert( registry.put!false(new C('1')));
    assert( registry.put!I(new C('2')));
    assert( registry.put(new S('$')));
assert(!registry.put!false(new C('3'))); // Optionally protect existing entries.

    assert(registry.get!(I).c == '2');
    assert(registry.get!(C).c == '1');
    assert(registry.remove!I);
    assert(registry.get!I  is null);
    assert(registry.get!C !is null);

    assert(registry.get!(S*).c == '$');
    assert(registry.get!(int*) is null);
}
////////////////////////////////

NOTE: If you only need one Registry instance in per thread, then you probably don't need the associative array at all; instead just use a template like this:

////////////////////////////////
template registered(Indirect)
if(is(Indirect : T*, T) // Support structs, static arrays, etc.
    || is(Indirect == interface) || is(Indirect == class))
{
/* This uses thread local storage (TLS). Sharing across the entire process is possible too, but would require synchronization of the
    registered objects, not just this reference/pointer: */
    private Indirect indirect = null;

    /// Return true if the registry was updated, false if not.
    bool put(bool overwrite = true)(Indirect i) @safe
    {
        bool updated = (indirect !is i);
        static if(overwrite) {
            indirect = i;
        } else {
            updated &= (indirect is null);
            if(updated)
                indirect = i;
        }
        return updated;
    }

    bool remove() @safe {
        bool updated = (indirect !is null);
        indirect = null;
        return updated;
    }

    /** Returns a reference (for interfaces and classes) or
a pointer (for everything else) if the type has been registered,
    and null otherwise. **/
    Indirect get() @safe {
        return indirect; }
}
@safe unittest {
    static interface I {
        char c() const pure @safe nothrow @nogc;
    }
    static class C : I {
        private char _c;
        this(char c) inout pure @safe nothrow @nogc {
            this._c = c; }
        override char c() const pure @safe nothrow @nogc {
            return _c; }
    }
    static struct S {
        char c = 'S';
        this(char c) inout pure @safe nothrow @nogc {
            this.c = c; }
    }

    assert( registered!(C).put!false(new C('1')));
    assert( registered!(I).put(new C('2')));
    assert( registered!(S*).put(new S('$')));
assert(!registered!(C).put!false(new C('3'))); // Optionally protect existing entries.

    assert(registered!(I).get.c == '2');
    assert(registered!(C).get.c == '1');
    assert(registered!(I).remove());
    assert(registered!(I).get  is null);
    assert(registered!(C).get !is null);

    assert(registered!(S*).get.c == '$');
    assert(registered!(int*).get is null);
}
////////////////////////////////

Aye, I'm using hashes. The idea is to support either D interfaces or structs with arbitrary content.

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.

`I` is *not* the type of an interface instance, it is the type of a reference to an instance of the interface.

So `I i` is a reference to the instance, which itself holds a reference to the implementation, like a reference to a delegate (pointer to (ctx, fn))?

No. What you are calling "the implementation" is the same thing as "the instance". `I i` is a reference into the interior of the implementing class instance, offset such that it points at the virtual function table pointer for that interface. The lowering is something like this:

////////////////////////////////
interface I { ... }
class C : I { ... }
struct C_Instance {
    void* __vtblForC;
    // ... other junk
    void* __vtblForI;
    // ... explicit and inherited class fields, if any.
}

C c = new C; // Works like `C_Instance* c = new C_Instance;`
I i = c; // Works like `void** i = (C_Instance.__vtblForI.offsetof + cast(void*) c);`
////////////////////////////////

You can prove this to yourself by inspecting the raw numerical addresses. You will find that the address used by the interface reference points to a location *inside* the instance of C:

////////////////////////////////
void main() {
    import std.stdio : writeln;

    static interface I { char c() @safe; }
    static class C : I { override char c() @safe { return 'C'; } }

    C c = new C;
    const cStart = cast(size_t) cast(void*) c;
    const cEnd = cStart + __traits(classInstanceSize, C);
    I i = c;
    const iStart = cast(size_t) cast(void*) i;
    writeln(cStart <= iStart && iStart < cEnd);
}
////////////////////////////////

Thus cast(void*) produces something like *(ctx, fn).

No. See above.

Reply via email to