Overloading based on attributes - is it a good idea?
Jonathan Marler
johnnymarler at gmail.com
Wed May 29 02:45:37 UTC 2019
On Wednesday, 29 May 2019 at 01:14:04 UTC, Walter Bright wrote:
> On 5/28/2019 3:12 PM, Jonathan Marler wrote:
>> You are correct that call graphs are not acyclic, however, the
>> inference of attributes only applies to templated-functions,
>> not regular functions. So we don't need to walk the entire
>> graph to determine whether or not a template is allowed to use
>> the GC, you only need to trace each template to it's nearest
>> non-template function.
> Your algorithm will have to work for the worst case, which is
> all template code. This is hardly unreasonable, as Phobos is
> nearly entirely templates, with cyclical expansions. Worse,
> it's not just attribute inference, you proposed different code
> paths for different attributes, meaning the graph changes, too.
>
> It's a combinatorial explosion.
Well, I didn't propose an algorithm. I was just providing a
visual aid to your graph analogy. My suggesion has nothing to do
with attribute inference on templates. If it did then you are
completely right that this would be a combinatorial explosion.
The idea is actually pretty "dumb" at its core. What's the
simplest way to pass this information to a template? Just add a
template parameter.
foo(bool nogc, bool nothrow, bool pure,...)()
{
// logic to do something different based on template
parameters
}
Then we need a way infer these attributes when we aren't inside a
template. You can do this today with things like:
foo!(__traits(compiles, new Object), __traits(compiles, throw new
Object), ...)();
This function call is long, but the point of it is that this same
call will pass the correct information based on whether the
surrounding scope is nogc and/or nothrow. It infers the
attributes before instantiating the template and passes the
results to it. But keep in mind, it is not inferring attributes
for the template, it is only passing information about the caller
to the template which it may or may not use.
But I think everyone can immediately see the problem with this.
We can't add these parameters to every single template, and then
add these __traits to every single template instantiation. There
would be more boiler-plate than actual code!
So came the idea of "implicit template parameters". The idea
would be that any template could access a set of implicit
variables to customize behavior. And to prevent an exponential
explosion of template instances, you would only consider implicit
template parameters when they are used, i.e.
template foo()
{
static if (__traits(requiresNothrow))
auto foo() nothrow { ... }
else
auto foo() { ... }
}
void bar1() { foo(); }
void bar2() nothrow { foo(); }
When the compiler sees the `requiresNoThrow` trait being used
inside a template, it notes that it is using one of the known
implicit template parameters, meaning that it will be
instantiated differently when the implicit "requiresNothrow"
template parameter changes, just like it would if it was a normal
template parameter.
So in this example, the two calls to foo in bar1 and bar2
actually instantiate 2 versions of foo, even though they have the
same "explicit template arguments", they have different "implicit
template arguments".
To understand how this works, note that implicit and explicit
template parameters work the same way. So it would function just
as if you had done this:
template foo(bool nothrow)
{
static if (nothrow)
auto foo() nothrow { ... }
else
auto foo() { ... }
}
void bar1() { foo!(__traits(compiles, new Object))(); }
void bar2() nothrow { foo!(__traits(compiles, new Object))(); }
It's just that now you don't need the boilerplate.
Is it a good idea? I don't know, but I think it's worth some
thought.
More information about the Digitalmars-d
mailing list