Thread overview
Map type to class instance at compile-time
Oct 26, 2016
pontius
Oct 26, 2016
Ali Çehreli
Oct 27, 2016
pontius
Oct 27, 2016
Ali Çehreli
Nov 05, 2016
pontius
October 26, 2016
Apologies for the long post or stupid questions, I only started to learn D today.
I have a use case where various types (A, B, C) need to be associated with instances of different classes (ManagerA, ManagerB, ManagerC). A certain object (globalManager) should redirect operations on those types to their respective managers at compile-time. I intend to use it in the following way:

class GlobalManager(TypeToManagerMapping) {
    public void process(T)(T t) {
        // Must be resolved at compile-time
        TypeToManagerMapping.getManagerForType(T).process(t);
    }
}

// All these types can be completely unrelated, i.e. no base classes
struct A {}
alias ManagerA = DefaultManager!(A)
struct B {}
alias ManagerB = DefaultManager!(B)
struct C {}
alias ManagerC = SomeCustomManager;

// Calls to globalManager should be redirected to these at compile-time
auto mgrA = new ManagerA();
auto mgrB = new ManagerB();
auto mgrC = new ManagerC();

// This is my problem, see below
alias TypeToManagerMapping = ...;

// Pass the mapping as a template parameter so that it can be resolved at compile-time
auto globalManager = new GlobalManager!(TypeToManagerMapping)();

// The following is the module user's code.
// The user may not be aware of mgrA, mgrB, etc, only of globalManager

A a = A();
B b = B();
C c = C();

// Redirection to managers in the following operations
// must be resolved at compile-time

// Should turn into mgrA.process(a), etc
globalManager.process(a);
globalManager.process(b);
globalManager.process(c);

So, I need to map a type (e.g. A) to an instance (e.g. mgrA). I have managed to implement a compile-time map from type to value (by the way, how do I get rid of TValue argument in TypeValuePair? it can be deduced, but I failed to get it working with an eponymous template):

struct TypeValuePair(TKey_, TValue, TValue value_) {
	alias TKey = TKey_;
	static const TValue value = value_;
}

struct StaticMap(THead, Args...) {
	template get(T) {
		static if (Args.length < 0) {
			static assert(false, "StaticMap does not contain this key");
		} else static if (is(T == THead.TKey)) {
			alias get = THead.value;
		} else {
			alias get = StaticMap!(Args).get!(T);
		}
	}
}

This works nicely for mapping types to literal values:

alias TypeMap = StaticMap!(
	TypeValuePair!(string, string, "a string"),
	TypeValuePair!(int, string, "an int"),
	TypeValuePair!(bool, string, "a bool")
);
writeln(TypeMap.get!(int));

But fails with "variable cannot be read at compile-time" when I try to pass a class instance in there:

alias TypeMap = StaticMap!(
	TypeValuePair!(A, ManagerA, mgrA)
);
TypeMap.get!(A).process(a);

So, how do I resolve type-to-instance mapping at compile-time so that my user only needs to call globalManager and not know anything about individual managers?

I could easily do this with typeid() calls, but the solution must be purely compile-time (for learning purposes; let's say my code is performance-critical and the lookup would take considerable amount of time).
October 26, 2016
On 10/26/2016 10:48 AM, pontius wrote:
> Apologies for the long post or stupid questions, I only started to learn
> D today.

Looking at what you've achieved in one day, we really need you! :)

> I have a use case where various types (A, B, C) need to be associated
> with instances of different classes (ManagerA, ManagerB, ManagerC). A
> certain object (globalManager) should redirect operations on those types
> to their respective managers at compile-time. I intend to use it in the
> following way:
>
> class GlobalManager(TypeToManagerMapping) {
>     public void process(T)(T t) {
>         // Must be resolved at compile-time
>         TypeToManagerMapping.getManagerForType(T).process(t);
>     }
> }
>
> // All these types can be completely unrelated, i.e. no base classes
> struct A {}
> alias ManagerA = DefaultManager!(A)
> struct B {}
> alias ManagerB = DefaultManager!(B)
> struct C {}
> alias ManagerC = SomeCustomManager;
>
> // Calls to globalManager should be redirected to these at compile-time
> auto mgrA = new ManagerA();
> auto mgrB = new ManagerB();
> auto mgrC = new ManagerC();
>
> // This is my problem, see below
> alias TypeToManagerMapping = ...;
>
> // Pass the mapping as a template parameter so that it can be resolved
> at compile-time
> auto globalManager = new GlobalManager!(TypeToManagerMapping)();
>
> // The following is the module user's code.
> // The user may not be aware of mgrA, mgrB, etc, only of globalManager
>
> A a = A();
> B b = B();
> C c = C();
>
> // Redirection to managers in the following operations
> // must be resolved at compile-time
>
> // Should turn into mgrA.process(a), etc
> globalManager.process(a);
> globalManager.process(b);
> globalManager.process(c);

void report(M, T)() {
    import std.stdio : writefln;
    writefln("%s is managing an object of %s", M.stringof, T.stringof);
}

class DefaultManager(T) {
    void manage(T t) {
        report!(typeof(this), T);
    }
}

class SomeCustomManager {
    void manage(C c) {
        report!(SomeCustomManager, C);
    }
}

struct A {}
alias ManagerA = DefaultManager!(A);
struct B {}
alias ManagerB = DefaultManager!(B);
struct C {}
alias ManagerC = SomeCustomManager;

/*/ You can use this:

alias TypeToManagerMapping = AliasSeq!(A, ManagerA,
                                       B, ManagerB,
                                       C, ManagerC);
 However, if you already have symbolic mapping from T to ManagerT, you can use string mixins as well
*/
template ManagerRegistrationFor(T) {
    // Just a couple of convenience functions
    string managerType() { return "Manager" ~ T.stringof; }
    string managerName() { return "mng" ~ T.stringof; }

    // This is the manager object
    mixin(managerType() ~ ' ' ~ managerName() ~ ';');

    // This is the initialization of it
    static this() {
        mixin(managerName() ~ `= new ` ~ managerType() ~ ';');
    }

    void manage(T obj) {
        // Dispatches to the manager
        mixin(managerName() ~ ".manage(obj);");
    }
}

struct GlobalManager {
    void process(T)(T t) {
        manage(t);
    }
}

GlobalManager globalManager;

mixin ManagerRegistrationFor!A;
mixin ManagerRegistrationFor!B;
mixin ManagerRegistrationFor!C;

/* The above could be specified with an AliasSeq (uncompiled):

alias ManagedTypes = AliasSeq!(A, B, C);

This loop can be inside a template like RegisterManagedTypes

foreach (T; ManagedTypes) {
    mixin ManagerRegistrationFor!T;
}

And then:

mixin RegisterManagedTypes;

*/

void main() {
    A a = A();
    B b = B();
    C c = C();
    globalManager.process(a);
    globalManager.process(b);
    globalManager.process(c);
}

There are different approaches but I think the solution above achieves what you want:

DefaultManager!(A) is managing an object of A
DefaultManager!(B) is managing an object of B
SomeCustomManager is managing an object of C

Ali

October 27, 2016
On Wednesday, 26 October 2016 at 18:19:33 UTC, Ali Çehreli wrote:
> There are different approaches but I think the solution above achieves what you want:
>
> DefaultManager!(A) is managing an object of A
> DefaultManager!(B) is managing an object of B
> SomeCustomManager is managing an object of C
>
> Ali

Thanks, your solution does work! I am still not familiar with mixins and haven't though about using them.
I refactored it so that it accepts a manager instance:

template ManagerRegistrationFor(T, alias mgr) {
    typeof(mgr) thisComponentMgr;

    static this() {
        thisComponentMgr = mgr;
    }

    void manage(T obj) {
        thisComponentMgr.manage(obj);
    }
}

struct GlobalManager {
    void process(T)(T t) {
        manage(t);
    }
}

GlobalManager globalManager;

mixin ManagerRegistrationFor!(A, new ManagerA());
const ManagerB mgrB = new const ManagerB();
mixin ManagerRegistrationFor!(B, mgrB);
mixin ManagerRegistrationFor!(C, new ManagerC());


I have an issue with incapsulating this code in another module, though. I should have probably clarified that Managers can be defined both inside my library and in separate modules by my library users. If I move template ManagerRegistrationFor and struct GlobalManager to e.g. manager.d and attempt to define ManagerA,B,C and call ManagerRegistrationFor(...) in main.d, it doesn't compile:

1) Error: template instance manager.GlobalManager.process!(A) error instantiating
This happens 3 times in main.d at globalManager.process(a); and the other to .process calls.
2) Error: undefined identifier 'manage', did you mean module 'manager'?
This error points to the body of GlobalManager.process in manager.d (it also occurs 3 times).

What would be the right way to incapsulate this mixin and GlobalManager to a separate module?
October 27, 2016
I admit that I can't come up with a solution right now. I hope others can see a way out of what I describe below. :-/

On 10/27/2016 11:33 AM, pontius wrote:
> On Wednesday, 26 October 2016 at 18:19:33 UTC, Ali Çehreli wrote:
>> There are different approaches but I think the solution above achieves
>> what you want:
>>
>> DefaultManager!(A) is managing an object of A
>> DefaultManager!(B) is managing an object of B
>> SomeCustomManager is managing an object of C
>>
>> Ali
>
> Thanks, your solution does work! I am still not familiar with mixins and
> haven't though about using them.
> I refactored it so that it accepts a manager instance:
>
> template ManagerRegistrationFor(T, alias mgr) {
>     typeof(mgr) thisComponentMgr;
>
>     static this() {
>         thisComponentMgr = mgr;
>     }
>
>     void manage(T obj) {
>         thisComponentMgr.manage(obj);
>     }
> }
>
> struct GlobalManager {
>     void process(T)(T t) {
>         manage(t);

The problem is with that line. In the previous design, ManagerRegistrationFor would generate a manage() template instance for T and mix it in to the scope. As a result manage(t) would be bound to it the correct template instance.

Now, because manager.d does not have that instance here, it needs to refer to it with the full template name:

    ManagerRegistrationFor!(T, ???).manage(t);

would work but we don't have the 'alias mgr' argument to refer to it (hence my question marks). I don't think it's ever possible to fully-qualify a template mixin that has an alias argument. (Yes, it would be possible if the user also had access to the same aliased symbol.)

So, I think the problem is with the new design; we need to get rid of that alias parameter and pass the manager object as a runtime parameter.

Ali

November 05, 2016
On Thursday, 27 October 2016 at 19:16:03 UTC, Ali Çehreli wrote:
> The problem is with that line. In the previous design, ManagerRegistrationFor would generate a manage() template instance for T and mix it in to the scope. As a result manage(t) would be bound to it the correct template instance.
>
> Now, because manager.d does not have that instance here, it needs to refer to it with the full template name:
>
>     ManagerRegistrationFor!(T, ???).manage(t);
>
> would work but we don't have the 'alias mgr' argument to refer to it (hence my question marks). I don't think it's ever possible to fully-qualify a template mixin that has an alias argument. (Yes, it would be possible if the user also had access to the same aliased symbol.)
>
> So, I think the problem is with the new design; we need to get rid of that alias parameter and pass the manager object as a runtime parameter.
>
> Ali

Heck, I should have noticed that the overloads are created in the wrong module. I would like to use alias here, because instantiation via string mixin places arbitrary restrictions on my users' constructors. I hope a better way is possible.
Nevertheless, thank you for your help, the examples were very useful for me.