www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Variadic template parameters, refactor a long chain of static if's to

reply realhet <real_het hotmail.com> writes:
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 = 
       else static if(same!(typeof(a), disabled)) r.enabled = 
       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 

     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 

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!
Jun 11 2019
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
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.
Jun 11 2019
parent realhet <real_het hotmail.com> writes:
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; } } } }
Jun 28 2019