Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

opDispatch and compile time parameters

Tags:

d

How can I use opDispatch for forwarding to method with compile time parameters. See code below:

import std.stdio;

struct B{
    auto p1(T)(T arg) {
        writeln( "p1: ", arg );
    }
    auto p2(T, int C)(T s) {
        writeln( "p2: ", s, " / ", C);
    }
}

struct C(T) {
    T b;

    auto opDispatch(string s, Args...)(Args args) {
        mixin("b."~s)(args);
    }
}

void main() {
     C!B b;
     //fine: compiler is smart enough
    b.p1("abc");
    //oops: "no property 'p2' for type ..."
    b.p2!(int, 10)(5);
    B origB;
    //fine:
    origB.p2!(int, 10)(5);
 }

EDIT

Replace class with struct: avoid usage of CTFE for field initialization with new. It is not related to my question.

like image 765
sibnick Avatar asked Oct 07 '15 17:10

sibnick


Video Answer


2 Answers

D template system is very powerful. This is more generic solution:

import std.stdio;
class B {
    auto p1(T)(T arg) { writeln( "p1: ", arg ); }
    auto p2(T, int C)(T s) { writeln( "p2: ", s, " / ", C); }
}
class C(T) {
    T b = new T;
    template opDispatch(string s) {
        template opDispatch(TARGS...) {
            auto opDispatch(ARGS...)(ARGS args) {
                static if(TARGS.length) return mixin("b." ~ s ~ "!TARGS(args)");
                else return mixin("b." ~ s ~ "(args)");
            }
        }
    }
}

void main() {
    auto b = new C!(B)();
    b.p1("abc");
    b.p2!(int, 10)(5);
}

http://dpaste.dzfl.pl/791c65d0e4ee

like image 146
Jack Applegame Avatar answered Sep 29 '22 10:09

Jack Applegame


If indeed this is not possible with opDispatch as Adam D. Ruppe's answer suggests, then your only recourse might be to turn to string mixins, which are a bit ugly but incredibly powerful. Unless using opDispatch is a hard requirement, string mixins might be the only way.

Luckily, most of the work to do this is already done (and it's more involved than you might think). The code itself is hairy, but all you need to do is as follows:

import std.stdio;

class B{
    auto p1(T)(T arg) {
        writeln( "p1: ", arg );
    }
    auto p2(T, int C)(T s) {
        writeln( "p2: ", s, " / ", C);
    }
}

class C(T) {
    T b = new T;

    mixin(forwardToMember!(b, "p1", "p2"));
}

void main() {
    auto b = new C!(B)();
    b.p1("abc");

    //This now compiles and correctly forwards to b.b.p2
    b.p2!(int, 10)(5);
}

I've included the code below with all the unittests stripped. It's important to note that forwardToMember currently doesn't support overloads for regular functions; it'll just pick the first instance of the specified function that it finds. I think they should work for template functions, however.

import std.traits;
import std.meta;

private alias isSomeStringType(alias str) = isSomeString!(typeof(str));

template forwardToMember(alias member, symbols...)
if (symbols.length > 0 && allSatisfy!(isSomeStringType, symbols))
{
    static if (symbols.length == 1)
    {
        static assert(hasMember!(typeof(member), symbols[0]),
            "Cannot dispatch: member '" ~ member.stringof ~
                "' does not support method '" ~ symbols[0] ~ "'");

        enum forwardToMember = genWrapperMixin!(member, symbols[0]);
    }
    else
    {
        enum forwardToMember = forwardToMember!(member, symbols[0]) ~ forwardToMember!(member, symbols[1..$]);
    }
}

private enum SymbolKind
{
    function_,
    property,
    templateFunction,
    fieldFunction,
    field,
    aliasableSym,
}

//Ugly hack but there's no other way to do this
private template isTemplateFunction(f...)
if (f.length == 1)
{
    import std.algorithm: among, balancedParens, canFind, count;

    static if (!__traits(isTemplate, f[0]))
    {
        enum isTemplateFunction = false;
    }
    else
    {
        enum fstr = f[0].stringof;

        //A template function's .stringof is of the format <function name>(<template args>)(<function args>)
        //so match on the number of brackets to determine whether it's a template function or not
        enum isTemplateFunction = __traits(isTemplate, f)
                                    && fstr.balancedParens('(', ')')
                                    && (fstr.canFind("if")
                                            || fstr.count!(c => cast(bool)c.among!('(', ')')) == 4);
    }
}

private template getSymbolKind(Aggregate, string symbol)
{
    import std.traits;
    import std.typetuple;

    enum getMemberMixin = "Aggregate." ~ symbol;

    //Appears in Aggregate.tupleof so it must be a field
    static if (staticIndexOf!(symbol, FieldNameTuple!Aggregate) > -1)
    {
        //Check if it's a regular field or a function pointer
        static if (isSomeFunction!(mixin(getMemberMixin)))
            enum getSymbolKind = SymbolKind.fieldFunction;
        else
            enum getSymbolKind = SymbolKind.field;
    }
    else
    {
        static if (isSomeFunction!(mixin(getMemberMixin))
                        && !__traits(isStaticFunction, mixin(getMemberMixin))
                        || isTemplateFunction!(mixin(getMemberMixin)))
        {
            static if (isTemplateFunction!(mixin(getMemberMixin)))
                enum getSymbolKind = SymbolKind.templateFunction;
            else static if (functionAttributes!(mixin(getMemberMixin)) & FunctionAttribute.property)
                enum getSymbolKind = SymbolKind.property;
            else
                enum getSymbolKind = SymbolKind.function_;
        }
        //If it's not a member function/property then it should be an aliasable static symbol
        else static if (__traits(compiles, { alias _ = Alias!(mixin(getMemberMixin)); }))
            enum getSymbolKind = SymbolKind.aliasableSym;
        else
            static assert(0, "Error: " ~ Aggregate.stringof ~ "." ~ symbol ~ " is not a member function, field, or aliasable symbol");
    }
}

private template genWrapperMixin(alias member, string symbol)
{
    import std.algorithm: among;
    import std.string: format;

    enum symbolKind = getSymbolKind!(typeof(member), symbol);

    static if (symbolKind.among!(SymbolKind.function_, SymbolKind.property, SymbolKind.fieldFunction))
    {
        alias MethodType = FunctionTypeOf!(mixin("member." ~ symbol));
        enum funAttrs = functionAttributes!MethodType;
        enum methodIsStatic = __traits(isStaticFunction, mixin("member." ~ symbol));
        enum funAttrStr = getFunctionAttributeStr(funAttrs) ~ (methodIsStatic ? " static" : "");

        //Workaround Issue 14913
        enum returnStr = funAttrs & FunctionAttribute.return_ ? "return" : "";

        enum genWrapperMixin = q{
            %3$s
            auto ref %2$s(ParameterTypeTuple!(FunctionTypeOf!(%1$s.%2$s)) args) %4$s
            {
                import std.functional: forward;

                return %1$s.%2$s(forward!args);
            }
        }
        .format(member.stringof, symbol, funAttrStr, returnStr);
    }
    else static if (symbolKind == SymbolKind.templateFunction)
    {
        enum genWrapperMixin =  q{
            template %2$s(TemplateArgs...)
            {
                auto ref %2$s(FunArgs...)(auto ref FunArgs args)
                {
                    import std.functional: forward;

                    return %1$s.%2$s!(TemplateArgs)(forward!args);
                }
            }
        }
        .format(member.stringof, symbol);
    }
    else static if (symbolKind == SymbolKind.field)
    {
        alias FieldType = typeof(mixin("member." ~ symbol));
        alias FA = FunctionAttribute;
        enum attrStr = getFunctionAttributeStr(FA.pure_ | FA.nothrow_ | FA.safe | FA.nogc);
        enum genWrapperMixin = q{
            @property %3$s %4$s %1$s()
            {
                return %2$s.%1$s;
            }

            @property %3$s void %1$s(%4$s val)
            {
                %2$s.%1$s = val;
            }
        }
        .format(symbol, member.stringof, attrStr, FieldType.stringof);
    }
    else static if (symbolKind == SymbolKind.aliasableSym)
    {
        enum genWrapperMixin = q{
            alias %1$s = %2$s.%1$s;
        }
        .format(symbol, member.stringof);
    }
    else
        static assert(member.stringof ~ "." ~ symbol ~ " has unexpected kind '" ~ symbolKind.to!string);
}

private string getFunctionAttributeStr(FunctionAttribute funAttrs)
{
    import std.algorithm: among, filter, joiner, map, strip;
    import std.conv: to;

    string funAttrStr;
    with (FunctionAttribute)
    {
        funAttrStr = [EnumMembers!FunctionAttribute]
                        .filter!(e => (funAttrs & e) && e != none && e != ref_ && e != return_)
                        .map!(e => e.to!string.strip('_'))
                        .map!(s => s.among!("safe", "trusted", "system", "nogc", "property") ? '@' ~ s : s)
                        .joiner(" ")
                        .to!string;
    }

    return funAttrStr;
}
like image 35
Meta Avatar answered Sep 29 '22 11:09

Meta