Thread overview
Variadic template parameters, refactor a long chain of static if's to 'functions'.
Jun 11, 2019
realhet
Jun 11, 2019
Adam D. Ruppe
Jun 28, 2019
realhet
June 11, 2019
Hi again,

I'm trying to do variadic parameter processing, and I have the following function that works fine:

  static bool same(T1, T2)(){
    pragma(msg, "same? "~T1.stringof~" "~T2.stringof);
    return is(immutable(T1)==immutable(T2));
  }

  Btn btn(T...)(string params, T args){
    enum captureIntID = true,
         captureBoolEnabled = true,
         captureFunct = true;

    Args r; //Arg is a struct to store all the possible parameters in runtime
    static foreach(a; args){
      //process id parameter
           static if(same!(typeof(a), id)) r.id = a.val;
      else static if(captureIntID && (same!(typeof(a), int) || same!(typeof(a), uint))) r.id = a;

      //process enabled/disabled
      else static if(same!(typeof(a), enabled )) r.enabled = a.val;
      else static if(same!(typeof(a), disabled)) r.enabled = !a.val;
      else static if(captureBoolEnabled && same!(typeof(a), bool) ) r.enabled = a;

      //process data events
      else static if(same!(typeof(a), onClick  )) r.events.onClick   = a.f;
      else static if(same!(typeof(a), onPress  )) r.events.onPress   = a.f;
      else static if(same!(typeof(a), onRelease)) r.events.onRelease = a.f;
      else static if(same!(typeof(a), onChange )) r.events.onChange  = a.f;
      else static if(same!(typeof(a), onTrue   )) r.events.onTrue    = a.f;
      else static if(same!(typeof(a), onFalse  )) r.events.onFalse   = a.f;
      else static if(same!(typeof(a), onBool   )) r.events.onBool    = a.f;
      else static if(typeof(a).stringof.startsWith("void delegate()"    )) r.events.onClick = a;
      else static if(typeof(a).stringof.startsWith("void delegate(bool)")) r.events.onBool = a;

      else static assert(false, "Unsupported type "~typeof(a).stringof);
    }

    return new Btn(params, r.id, r.enabled, r.events);
  }


The long chain of is's are required to be able to show the error message at the end when an unhandled parameter type is found.

I will have more of these and want to reuse these ifs in other functions as well, so I decided to take out the first 2 if's and put it into a template function:

  static bool processId(bool captureIntId, alias r, alias a)(){
    mixin("alias ta = typeof("~a.stringof~");");
    pragma(msg, "ta "~ta.stringof);
         static if(same!(ta, id)) { r = a.val; return true; }
    else static if(captureIntId && (same!(ta, int) || same!(ta, uint))) { r = a; return true; }
    else return false;
  }

later I call it with:

  static if(processId!(true, r.id, a)){}
  else static if(...

'a' is the variadic foreach variable (_param_1 and so on),
'r.id' is the result field from an Arg structure.
Both 'a' and 'r.id' are existing and compiling, also the pragma messages are good, I see it can find the right types, but I have the following error for every variadic parameter:

Error: template instance processId!(true, id, _param_1) processId!(true, id, _param_1) is nested in both Args and btn

id is a struct. And also a field in the Args struct. I changed it to id1 in the Args struct ant the error remains the same.
Everything resides in the scope of a class called ContainerBuilder.

Is there a way to do this elegantly?
Maybe if I put everything in a very large chain of if's and enable the required parameters with a set of flags, but that's not structured well, and also needs the biggest possible set of runtime parameter storage to store the processed parametest.

Here's an example using the above function:
btn(`Yes flex=1`, id(54231), enabled(true), { lastClickedBtn = "Yes"; hehe = true; })
checkbox("Checkbox_1", 12345432, (bool b){ hehe = b; })

I will have a lot of controls and a lot of parameter types. I wanna do something like in the old VisualBasic, where you can skip a lot of default parameters, and also access the only few you need by name. Something like this: btn("caption", hint:"blabla", icon:"icon.ico"), but as I currently know this is not implemented in D.

Thank You in advance!


June 11, 2019
On Tuesday, 11 June 2019 at 09:26:56 UTC, realhet wrote:
>   static bool processId(bool captureIntId, alias r, alias a)(){
>     mixin("alias ta = typeof("~a.stringof~");");

As I have been saying a lot, mixin and stringof should almost never be used together. You could write this a lot easier:

alias ta = typeof(a);

no need for mixin at all.


> Error: template instance processId!(true, id, _param_1) processId!(true, id, _param_1) is nested in both Args and btn

So Args is a runtime thing, but you are trying to use it in compile time. The compiler can handle one layer of this - it basically inlines a runtime function when you do that - but with more layers it gets lost.

What you'll need to do is to kinda embrace this and have a helper struct that returns the stuff by value. To avoid holding all the possible types, pass what you want to it as type arguments inside the function.

Check this out:


---

struct ID { int val; }
struct enabled { bool val; }
struct disabled { bool val; }

struct PossibleArguments(T...) {

        this(Args...)(Args args) {
                static foreach(arg; args) {{
                        enum idx = TindexOf!(typeof(arg));
                        static if(idx >= 0)
                                contents[idx] = arg;
                        else
                                static assert(0, "Unusable arg " ~ typeof(arg).stringof);
                }}
        }

        private T contents;
        private static int TindexOf(What)() {
                static foreach(idx, t; T) {
                        // you could use your isSame here
                        static if(is(t == What))
                                return cast(int) idx;
                }
                return -1;
        }

        auto opDispatch(string n)() {
                static foreach(idx, item; T)
                        if(item.stringof == n)
                                return contents[idx].val;
                assert(0);
        }
}

void btn(T...)(string params, T args) {
     // here's the usage: list args I want to support as CT,
     // then pass the other args as runtime
        auto r = PossibleArguments!(
                ID,
                enabled
        )(args);

        import std.stdio;
        writeln(r.opDispatch!"ID"); // or r.ID, but while debugging it helps to explicitly all r.opDispatch so you get the full error instead of "no such property"
        writeln(r.enabled);
}


void main() {
        // I enabled the first two, but not disabled, so this is an error
        //btn("ignored", ID(5), enabled(true), disabled(false));

        // but this works
        btn("ignored", ID(5), enabled(true));

        // and it can be reordered/ignored/etc
        btn("ignored", enabled(false));
}

---



So now you avoid the list of ifs thanks to that TindexOf helper thing while still getting the helpful user error message, and the opDispatch can make feel like you are accessing by name inside.

Extending it to other types is an exercise for you :) Particularly, the event ones will need a different approach of some sort.

I would suggest actually putting them in a wrapper struct so you can  still use this cool name trick.

But alternatively, you could special-case them in the two loops, like always knowing void delegate() is going to be named onClick. I can help you solve this later too if you wanna go that way, I just g2g right now lol.
June 28, 2019
On Tuesday, 11 June 2019 at 13:22:26 UTC, Adam D. Ruppe wrote:
> On Tuesday, 11 June 2019 at 09:26:56 UTC, realhet wrote:
>>   static bool processId(bool captureIntId, alias r, alias a)(){
>>     mixin("alias ta = typeof("~a.stringof~");");
>
> As I have been saying a lot, mixin and stringof should almost never be used together. You could write this a lot easier:
>...

Thank you for the long example, it helped me a lot!

The thing is now capable of:

- getting a specific type of parameter -> args.paramByType!(string[int]).writeln;

- including wrapper structs. In that case, optionally can fall back to the type inside the struct.

- handle and call events (structs or fallback as well)

- It is super easy now to make a function that can process a specific set of events:
    struct onClick          { void delegate() val; }
    ...
    struct onHold           { void delegate() val; }

    void processBtnEvents(T...)(bool click, bool state, bool chg, T args){
      if(click) args.paramCall!(onClick, true/*fallback to pure delegate*/);
      if(state) args.paramCall!onHold;
      if(chg){
        args.paramCall!onChange;
        if(state) args.paramCall!onPress;
             else args.paramCall!onRelease;
      }
    }

- Also I've found a way to exchange state values (for example a bool for a CheckBox control):
  - I can give it a pointer to a bool
  - Or I can give a getter and 0 or more setter(s): bool delegate(), void delegate(bool)
  - Can be functionPointers too, not just delegates.
  - example:
    void fIncrementer(T...)(T args){
      paramGetterType!T x;
      args.paramGetter(x);
      x = (x + 1).to!(paramGetterType!T);
      args.paramSetter(x);
    }
  - it is letting me use enumerated types too -> I will not have to write anything, and there will be an automatic RadioButtonGroup on the screen, just bu mentioning that variable. It will be awesome!

- Sorry for the lot of string processing and some mixin()s :D At the end of the code below, I've learned a lot while being able to write nicer code.

Thank you for your example code, I've learned a lot from it.

--------------------------------------------------------------
private{
  bool is2(A, B)() { return is(immutable(A)==immutable(B)); }

  bool isBool  (A)(){ return is2!(A, bool  ); }
  bool isInt   (A)(){ return is2!(A, int   ) || is2!(A, uint  ); }
  bool isFloat (A)(){ return is2!(A, float ) || is2!(A, double); }
  bool isString(A)(){ return is2!(A, string); }

  bool isSimple(A)(){ return isBool!A || isInt!A || isFloat!A || isString!A; }

  bool isGetter(A, T)(){
    enum a = A.stringof, t = T.stringof;
    return a.startsWith(t~" delegate()")
        || a.startsWith(t~" function()");
  }
  bool isSetter(A, T)(){
    enum a = A.stringof, t = T.stringof;
    return a.startsWith("void delegate("~t~" ")
        || a.startsWith("void function("~t~" ");
  }
  bool isEvent(A)(){ return isGetter!(A, void); } //event = void getter

  bool isCompatible(TDst, TSrc, bool compiles, bool compilesDelegate)(){
    return (isBool  !TDst && isBool  !TSrc)
        || (isInt   !TDst && isInt   !TSrc)
        || (isFloat !TDst && isFloat !TSrc)
        || (isString!TDst && isString!TSrc)
        || !isSimple!TDst && (compiles || compilesDelegate); //assignment is working. This is the last priority
  }
}

auto paramByType(Tp, bool fallback=false, T...)(T args){
  Tp res;

  enum isWrapperStruct = __traits(hasMember, Tp, "val") && Fields!Tp.length==1; //is it encapsulated in a wrapper struct?  -> struct{ type val; }

  enum checkDuplicatedParams = q{
    static assert(!__traits(compiles, duplicated_parameter), "Duplicated parameter type: %s%s".format(Tp.stringof, fallback ? "("~typeof(Tp.val).stringof~")" : ""));
    enum duplicated_parameter = 1;
  };

  static foreach_reverse(idx, t; T){
    //check simple types/structs
    static if(isCompatible!(typeof(res), t, __traits(compiles, res = args[idx]), __traits(compiles, res = args[idx].toDelegate))){
      static if(__traits(compiles, res = args[idx]))           res = args[idx];                else res = args[idx].toDelegate;
      mixin(checkDuplicatedParams);
    }else
    //check fallback struct.val
    static if(fallback && isWrapperStruct && isCompatible!(typeof(res.val), t, __traits(compiles, res.val = args[idx]), __traits(compiles, res.val = args[idx].toDelegate))){
      static if(__traits(compiles, res.val = args[idx]))                                          res.val = args[idx];                else res.val = args[idx].toDelegate;
      mixin(checkDuplicatedParams);
    }
  }

  static if(isWrapperStruct) return res.val;
                        else return res;
}

void paramCall(Tp, bool fallback=false, T...)(T args){
  auto e = paramByType!(Tp, fallback)(args);
  static assert(isEvent!(typeof(e)), "paramCallEvent() error: %s is not an event.".format(Tp.stringof));
  if(e !is null) e();
}

template paramGetterType(T...){
  static foreach(t; T){
    static if(isPointer!t){
      static if(isFunctionPointer!t){
        static if(Parameters!t.length==0)
          alias paramGetterType = ReturnType!t; //type function()
      }else{
        alias paramGetterType = PointerTarget!t; //type*
      }
    }else static if(isDelegate!t){
      static if(Parameters!t.length==0)
        alias paramGetterType = ReturnType!t; //type delegate()
    }
  }

  static assert(is(paramGetterType), "Unable to get paramGetterType");
}

void paramGetter(Tr, T...)(T args, ref Tr res){ //duplicate checking is in paramGetterType
  static foreach_reverse(idx, t; T){
    static foreach(t; T){
      static if((isFunctionPointer!t || isDelegate!t) && Parameters!t.length==0 && !is(ReturnType!t==void) && __traits(compiles, res = args[idx]().to!Tr)){
        res = args[idx]().to!Tr;
      }else static if(isPointer!t && __traits(compiles, res = (*args[idx]).to!Tr)){
        res = (*args[idx]).to!Tr;
      }
    }
  }
}

void paramSetter(Tr, T...)(T args, in Tr val){ //duplicates are allowed
  static foreach_reverse(idx, t; T){
    static foreach(t; T){
      static if((isFunctionPointer!t || isDelegate!t) && Parameters!t.length==1 && is(ReturnType!t==void) && __traits(compiles, args[idx](val.to!Tr))){
        args[idx](val.to!Tr);
      }else static if(isPointer!t && __traits(compiles, *args[idx] = val.to!Tr)){
        *args[idx] = val.to!Tr;
      }
    }
  }
}