Flexible Default Function Parameters via structs with Nullable Fields

Adam D. Ruppe destructionator at gmail.com
Tue Apr 30 15:17:00 UTC 2019


On Tuesday, 30 April 2019 at 13:44:00 UTC, Simen Kjærås wrote:
> Now, for the abomination that is 
> callMemberFunctionWithParamsStruct!(t, "f")(combined)... It's 
> just t.f(combined.tupleof) in a bad disguise, and I really 
> can't see the benefit.

If you are doing function parameters, there are two kinda fun 
things you can do. (Personally, I kinda prefer to just do 
hand-written builder patters, nicer to document, often easier to 
read, but this is D, so let's go nuts!)


First, this is an automatically generated struct with members 
corresponding to function parameters:

---

void foo(int a, string cool = "low temperature", int[] c = [1, 2, 
3]) {
	import std.stdio;
	writeln("a = ", a);
	writeln("cool = ", cool);
	writeln("c = ", c);
}

// this works for free functions, but not delegates, function 
pointers, or other callable objects
// it also will not automatically call a method, but you can 
build parameters for it.
struct ParamsFor(F...) if(F.length == 1) {
	static if(is(typeof(F[0]) Parameters == __parameters)) {
		static foreach(idx, _; Parameters) {
			static if(__traits(compiles, ((Parameters[idx .. idx + 1] i) 
=> i[0])()))
			mixin("
				Parameters[idx .. idx + 1][0] // type
				"~__traits(identifier, Parameters[idx .. idx + 1])~" // name
				= ((Parameters[idx .. idx + 1] i) => i[0])() // initial value
			;");
			else
			mixin("
				Parameters[idx .. idx + 1][0] // type
				"~__traits(identifier, Parameters[idx .. idx + 1])~" // name
				// no initial value
			;");
		}
	} else static assert(0, typeof(F[0]).stringof ~ " is not a plain 
callable");

	auto opCall()() {
		static if(__traits(compiles, F[0](this.tupleof)))
			return F[0](this.tupleof);
		else static assert(0, __traits(identifier, F[0]) ~ " is not 
callable this way since it needs a `this` object, do it yourself 
on the outside with obj.method(params.tupleof)");
	}
}

class Test {
	void foo(int a, int b = 10, int c = 20) {
		import std.stdio;
		writeln(a, " ", b, " ", c);
	}
}

void main() {
	ParamsFor!foo params;

	params.c = [4,5,6];

	params(); // calls foo(params.tupleof) for you


	ParamsFor!(Test.foo) p2;
	p2.c = 30;
	auto f = new Test();

	//p2(); // will static assert cuz of this

	f.foo(p2.tupleof); // use this instead
}

---



But there, required parameters can be left out too - you don't 
have to set anything. (It also doesn't work with const params and 
other such troubles, but that really complicates this idea - and 
is part of why I prefer a hand-written builder thing, so you can 
handle all those details explicitly.)


We can solve that with a constructor. Right below the first 
static if in the example, add:

---
		static if(!__traits(compiles, ((Parameters _) {}) () )) {
			@disable this();
			this(Parameters params) {
				this.tupleof = params;
			}
		}
---

And now you get an obscure error if you don't specific parameters 
when creating the Params object. But.... eh I don't love it.



Regardless, still though, this stuff is kinda cool. And if you 
combine with the `with` statement:


---
void main() {
         // this assumes the version with the constructor
         // but if you didn't add that code, just remove
         // the 5 and thus ParamsFor!foo()
         with(ParamsFor!foo(5)) {
                 c = [4,5,6]; // set the param c...
                 opCall(); // call the function
         }
}
---

so yeah, kinda cool.


More information about the Digitalmars-d-announce mailing list