Thread overview
Generic structural recursion
Jan 26, 2022
H. S. Teoh
Jan 26, 2022
John Colvin
Jan 27, 2022
Ali Çehreli
January 26, 2022
Got stuck on this metaprogramming problem, and can't seem to find a way to make it work, just wondering if any of you guys can come up with a clean solution:

I have a bunch of nested structs of the form:

	struct S { double data; }
	struct T { S s1, s2; }
	struct U { S s, T[2] ts; }
	struct V { T t, U u; }
	... // etc.

and a bunch of functions that operate on structs of this form via structural recursion, of the following form (the exact number of parameters of type X differs across functions):

--------
X interpolate(X)(ref X result, double idx, X data1, X data2) {
	foreach (field; FieldNameTuple!X) {
		alias Type = typeof(__traits(getMember, X, field));
		if (is(Type == double))
			__traits(getMember, result, field) =
				(1.0-idx)*__traits(getMember, data1, field)
				+ idx*__traits(getMember, data2, field);
		else if (is(Type == struct)) {
			interpolate(
				__traits(getMember, result, field), idx,
				__traits(getMember, data1, field),
				__traits(getMember, data2, field));
		} else if (is(Type == U[n], U, size_t n)) {
			foreach (i; 0 .. n) {
				interpolate(
					__traits(getMember, result, field)[i], idx,
					__traits(getMember, data1, field)[i],
					__traits(getMember, data2, field)[i]);
			}
		}
	}
}
--------

IOW, given some number of structs of type X, each function recurses down the nested structs and invokes some operation (in this case an interpolation function) on the corresponding double fields of each argument.

As you can see above, there's a LOT of boilerplate needed for each function. Whenever I want to change the recursion (e.g., support dynamic arrays, or exclude certain types from the recursion) I have to modify every function by hand -- a tedious and error-prone process.  I'd like to factor out the code that recurses down the nested structs into a separate template function, say call it structuralRecurse, and have each of the operations simply call structuralRecurse with some delegate or function literal that would be invoked on each set of corresponding double fields.

But so far, I've not been able to do this in its full generality, i.e., something of the form:

------
void structuralRecurse(alias fun, T, size_t n)(T t0, T t1, ... T tn) {
	// invoke fun(t0.x.y.z, t1.x.y.z, ...); for each double field in T
}
------

Even using a giant string mixin so far hasn't been obvious, because of the recursive descent required. And I'd like if possible to avoid using giant string mixins for obvious reasons.

Anybody has any ideas?


T

-- 
If it breaks, you get to keep both pieces. -- Software disclaimer notice
January 26, 2022
On Wednesday, 26 January 2022 at 18:22:18 UTC, H. S. Teoh wrote:
> [...]

You could define a template that introspects a struct type and gives you a tuple of getters (each taking a struct and returning one of the doubles), then your functions that do real work would just loop over those getters. I suspect using the new(-ish) alias assign feature would help in defining that template.
January 26, 2022

On 1/26/22 1:22 PM, H. S. Teoh wrote:

>

X interpolate(X)(ref X result, double idx, X data1, X data2) {
foreach (field; FieldNameTuple!X) {
alias Type = typeof(__traits(getMember, X, field));
if (is(Type == double))
__traits(getMember, result, field) =
(1.0-idx)__traits(getMember, data1, field)
+ idx
__traits(getMember, data2, field);
else if (is(Type == struct)) {
interpolate(
__traits(getMember, result, field), idx,
__traits(getMember, data1, field),
__traits(getMember, data2, field));
} else if (is(Type == U[n], U, size_t n)) {
foreach (i; 0 .. n) {
interpolate(
__traits(getMember, result, field)[i], idx,
__traits(getMember, data1, field)[i],
__traits(getMember, data2, field)[i]);
}
}
}
}

This looks a lot like serialization. What you have done looks more specific to your use case though. Why not call a function recursively for each type encountered?

e.g.:

auto interpolate(alias fn, X : double)(ref X val1, ref X val2)
{
   static if(is(typeof(fn(val1, val2)))) return fn(val1, val2);
}

auto interpolate(alias fn, X)(ref X val1, ref X val2) if (is(X == struct))
{
   // loop and recurse
}
...

Then you can write a set of templates that act on the right types, and do the thing you want for that specific type, pass that in as fn.

I find splitting the work into distinct portions when doing these kinds of introspection tasks to be more straightforward and testable.

-Steve

January 27, 2022
On 1/26/22 10:41, John Colvin wrote:

> You could define a template that introspects a struct type and gives you
> a tuple of getters

I've done something similar for ROS where data is a bunch of bytes but with a well-defined structure. The difference there is array elements are inline with their length encoded first.

So I introspected a FieldSkipper for each struct member which actually consisted of the FieldSkippers of the previous members (array skippers being smart and recursive to skip over the elements as well).

Then I had a FieldGetter that would use the corresponding FieldSkipper to find the right bytes. Fun stuff... :)

Ali