Optimizing recursive templates
Stefan Koch
uplink.coder at googlemail.com
Fri Oct 9 11:03:57 UTC 2020
On Thursday, 24 September 2020 at 19:49:44 UTC, Stefan Koch wrote:
> Hi Guys,
>
> I am starting this thread to talk about the feasibility of
> rewriting recursive templates as iterative internal forms.
>
Hi,
I very recently had a conversation about template optimization.
And during that an interesting example came up.
For a sequence of types (candidates) select the first one that is
convertible to a given conversion target (convTarget)
The naive way of writing this template is not even this bad.
---
alias Seq(T...) = T;
alias EmptySeq = Seq!()
template selectFirstConvertable(convTarget, candidates...)
{
static if (!candidates.length)
selectFirstConvertable = EmptySeq;
else static if (is(candidates[0] : convTarget))
selectFirstConvertable = candidates[0];
else
selectFirstConvertable =
selectFirstConvertable!(candidates[1 .. $]);
}
---
So if I wanted to get rid of the recursion here, what would I do?
....
I don't really know.
Let's write the type function:
---
type selectFirstConvertable(type convTarget, type[] candidates
...)
{
type result; // initialzed to ∅
foreach(c;candidates)
{
if (is(c : convTarget))
{
result = c;
break;
}
}
return result;
}
---
now let's search for commonalities.
a good one would be the exit condition of the loop
`if (is(c : convTarget))`
we can find a line in the template which almost looks like that
`else static if (is(candidates[0] : convTarget))`
we could substitute c for candidates[0] and get
`else static if (is(c : convTarget))`
which looks a lot like the condition in our loop.
so how can we determine that this will "break;" ?
Well as long as it does not recurse (directly or indirectly).
It must end the recursion.
That means if what's inside the static if branch does not
instantiate any template,
we can assume it to be termination.
is this true for the static if body in question?
yes. `selectFirstConvertable = candidates[0];`
does not recurse into itself.
So we've established a "break point" if you've pardon the pun :)
From that we can deduce that the 'exit' condition is
`is(candidates[0] : convTarget)`
We can further establish from `selectFirstConvertable =
candidates[0];`
that we essentially do `return candidates[0]`;
which gives us a rewrite rule
templateName = X;
can be rewritten as
return X;
let's apply those rewrite rule.
And annotate meaning.
template selectFirstConvertable(convTarget, candidates...)
{
static if (!candidates.length) // reversed
loop entrance condition
return EmptySeq;
else static if (is(candidates[0] : convTarget)) // loop exit
condition
return candidates[0];
else
return selectFirstConvertable!(candidates[1 .. $]); //
loop increment
}
Now that's more like a function wouldn't you say?
so let's build a loop
template selectFirstConvertable(convTarget, candidates...)
{
bool exitCondition = is(candidates[0] : convTarget);
if (!canidates.length)
return EmptySeq;
else while (!exitCondition) // hitting this means the entrance
condition was met
{
candidates = candidates[1 .. $] // problem is we can't do this!
// re-evaluate exit condition
exitCondition = is(candidates[0] : convTarget)
}
return candidates[0];
}
As we see, the loop necessitates for the language to be able to
store a tuple in a variable
and for that variable to be mutable!
Also we need to be able to actually interpret the loop.
Which (for this example) requires for CTFE to know about is
expressions
And for CTFE to turn Type into expressions (An ability which
exists internal to dmd but is not exposed to the user)
Alternatively we could of course implement another interpretation
system which would mirror most of CTFE but also allows
type variables.
In order to keep the systems unchanged.
The question then is, if we already have to do this.
Why not expose it to the user?
And when you follow that line of inquiry that leads you directly
to ...
Well you can figure that out for yourself ;)
Cheers,
Stefan
More information about the Digitalmars-d
mailing list