Tuple IFTI with implicit conversions

Reiner Pope some at address.com
Sat Oct 20 19:30:04 PDT 2007


Jari-Matti Mäkelä wrote:
> Reiner Pope wrote:
> 
>> (A follow-up from "Any ideas for lazy evaluation on varaible argument
>> functions?")
>>
>> Daniel Keep wrote:
>>> Sadly, I don't think there's any way to fix this.  The problem is that
>>> if you've got IFTI (which is what allows you to omit the explicit
>>> template instantiation), the arguments can't be very complex.  What you
>>> would need is something like this:
>>>
>>> void infoF(A...)(lazy DecayStaticArrays!(A) a) { ... }
>>>
>>> Where "DecayStaticArrays" turns all statically-sized arrays into
>>> dynamically-sized ones: so (char[2u], char[6u]) would become (char[],
>>> char[]).
>> This kind of problem with IFTI seems to come up from time to time. I
>> think the general case of this problem is when you want the parameter
>> types[1] to be something which the argument types[1] are implicitly
>> convertable to. This could be like the example above (where char[6u] is
>> implicitly convertable to lazy char[]) or perhaps some function which
>> takes the biggest integer type of the parameters:
>>
>> BiggestIntType!(A) max(A...)(BiggestIntType!(A)[] params...) { ... }
>>
>> Unfortunately, the above function won't work with IFTI. Normally, the
>> "standard" solution to this would be to have a wrapper template:
>>
>> BiggestIntType!(A) max(A...)(A a) {
>>      max_impl!(A)(a);
>> }
>>
>> BiggestIntType!(A) max_impl(A...)(BiggestIntType!(A)[] params...) { ... }
>>
>> so that the wrapper ensures IFTI still works, and it simply passes on
>> the argument types to the wrapper. Although this solution works here, it
>> doesn't solve Daniel's problem, and it ends up being repeated whereas I
>> think the language should essentially support doing it for you.
>>
>> More valuable would be the existence of some tuple in a function
>> template which was a tuple of the argument types[1] for a function. I'm
>> not fussed about the syntax, but the point is that argtypes is simple to
>> evaluate: just take typeof() of each of the argument expressions at the
>> call-site, and stick them together as a tuple. Since there's no pattern
>> matching involved, IFTI can't fail. So, the examples above would become:
>>
>> BiggestIntType!(A) max (argtypes A)(BiggestIntType!(A)[] params...) { ...
>> }
>>
>> and
>>
>> void infoF(argtypes A)(lazy DecayStaticArrays!(A) a) { ... }
>>
>> They look almost exactly the same, but the argtypes syntax says to the
>> compiler, "don't try to pattern-match the parameters against lazy
>> DecayStaticArrays!(A), instead just tell me what they are." Supposing
>> you used the above syntax, then it would have the rules:
>>    1. The argtypes parameter must always go last, even after variadic
>> parameters
>>    2. It is impossible to manually instantiate an argtypes parameter.
>>
>> What do you think?
> 
> Couldn't the polysemous values be used here? E.g. caller side calls the
> function with a set of possible type tuples. In the end the compiler could
> choose the best match(es) or throw an error on ambiguity. The char[] <->
> char[n] conversion is particularly annoying even in very trivial code - a
> template doesn't sound like a good idea.
> 
> Approach with polysemous values could be extended in other ways. IFTI
> functions could be overloaded. One thing that has caused me some headache
> is the IsExpression - it seems to sweep under the carpet the fact that
> template parameters cannot do proper static predicate dispatching. I'd like
> to see a way to specify a compile time boolean function that defines
> whether the pattern matches or not (arithmetics with types should have
> proper semantics then - this could be extended elsewhere too, but I won't
> touch that here) - a general case could look like:
> 
> void myFun(T1 : predicate_1, ..., Tn : predicate_n)(type_1 param_1, ...,
> type_n param_n)
> 
> I don't think moveing the predicates to the fn parameter side (BCS's
> proposal) is a good idea since it's something different that D does now.
> 
> Here's some related discussion http://lambda-the-ultimate.org/node/1655

In their simplest form, predicates solve a different problem. To give a 
concrete example of what I'm talking about, suppose I have a struct:

struct Wrapper(T)
{
     const bool IsWrapper = true;
     ...
     static Wrapper!(T) opImplicitCastFrom(T t) {...}
}

and I have a function which requires that it is called with arguments 
always wrapped in Wrapper (Wrapper!(T) might, for instance, be lazy(T), 
or a reference to T, so that it has to be wrapped on the calling side).

Suppose you had a predicate which said if some type, T, is a Wrapper of 
something else. It might look like:

template WrapperPredicate(T)
{
     const bool WrapperPredicate = is(T.IsWrapper == bool);
}

If you then wrote a templated function using this predicate, it wouldn't 
work:

void foo(T : WrapperPredicate!(T)) (T t) {}

void main() {
     foo(5); // error, int is not a Wrapper, so no template found
}

This seems to be where you say polysemous values help, ie when 
instantiating the template, you come up with a list of possible types 
for T, and see if any match the predicate. The problem is that this list 
can be infinitely long, if you have something like this:

struct Foo(T)
{
     static Foo!(T) opImplicitCastFrom(int i) {...}
}

While that might be a straw man, it still highlights the difficulty in 
the compiler being able to find that Wrapper!(int) has an 
opImplicitCastFrom(int). I think templates are too powerful for the 
compiler to determine all the possible implicitly-convertible types, so 
for problems like this, you need to say to the compiler, "here's how you 
find the type". Predicates alone don't do this, as they just say, "this 
type is (in)valid".

Interestingly, C++0x Concepts have concept_maps, which solve this 
problem by letting you tell the compiler "this is how to make this type 
match this concept."

---

However, I agree with you about the need for predicates. There's a neat 
trick which allows you to use them already in D, though:

void foo(T, bool T_predicate : true = IsValidParam!(T)) (T t) {...}

It works well, except that it doesn't allow specialization.

    -- Reiner



More information about the Digitalmars-d mailing list