www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Reducing template constraint verbosity? [was Re: Slides from my ACCU

reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 14 Dec 2010 10:47:05 -0500, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 On 12/14/10 7:33 AM, biozic wrote:

 I have a question about this and some pieces of code in the standard
 library, notably std.algorithm: some of the templated functions use
 template constraints even when no template overloading is taking place.
 Wouldn't some static asserts help print more accurate messages when
 these functions are misused?

 Nicolas

The intent is to allow other code to define functions such as e.g. "map" or "sort". Generally any generic function should exclude via a constraint the inputs it can't work on. That way no generic function chews off more than it can bite.

Redirected from another thread. Having written a few of these functions with template constraints, I wondered if there was ever any discussion/agreement on reducing verbosity when specializing template constraints? For instance, if you want two overloads of a template, one which accepts types A and one which accepts types B where B implicitly converts to A (i.e. a specialization), you need to explicitly reject B's when defining the overload for A's. For example: void foo(R)(R r) if(isRandomAccessRange!R) {...} void foo(R)(R r) if(isInputRange!R && !isRandomAccessRange!R) {...} It seems redundant to specify !isRandomAccessRange!R in the second overload, but the compiler will complain otherwise. What sucks about this is the 'definition' of the first overload is partially in the second. That is, you don't really need that clause in the second overload unless you define the first one. Not only that, but it makes the template constraints grow in complexity quite quickly. Just look at a sample function in std.array that handles 'the default' case: void popFront(A)(ref A a) if(!isNarrowString!A && isDynamicArray!A && isMutable!A && !is(A == void[])) Any idea how this can be 'solved' or do we need to continue doing things like this? My naive instinct is to use the declaration order to determine a match (first one to match wins), but that kind of goes against other overloads in D. -Steve
Dec 14 2010
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/14/10 10:08 AM, Steven Schveighoffer wrote:
 On Tue, 14 Dec 2010 10:47:05 -0500, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 On 12/14/10 7:33 AM, biozic wrote:

 I have a question about this and some pieces of code in the standard
 library, notably std.algorithm: some of the templated functions use
 template constraints even when no template overloading is taking place.
 Wouldn't some static asserts help print more accurate messages when
 these functions are misused?

 Nicolas

The intent is to allow other code to define functions such as e.g. "map" or "sort". Generally any generic function should exclude via a constraint the inputs it can't work on. That way no generic function chews off more than it can bite.

Redirected from another thread. Having written a few of these functions with template constraints, I wondered if there was ever any discussion/agreement on reducing verbosity when specializing template constraints? For instance, if you want two overloads of a template, one which accepts types A and one which accepts types B where B implicitly converts to A (i.e. a specialization), you need to explicitly reject B's when defining the overload for A's. For example: void foo(R)(R r) if(isRandomAccessRange!R) {...} void foo(R)(R r) if(isInputRange!R && !isRandomAccessRange!R) {...} It seems redundant to specify !isRandomAccessRange!R in the second overload, but the compiler will complain otherwise. What sucks about this is the 'definition' of the first overload is partially in the second. That is, you don't really need that clause in the second overload unless you define the first one. Not only that, but it makes the template constraints grow in complexity quite quickly. Just look at a sample function in std.array that handles 'the default' case: void popFront(A)(ref A a) if(!isNarrowString!A && isDynamicArray!A && isMutable!A && !is(A == void[])) Any idea how this can be 'solved' or do we need to continue doing things like this? My naive instinct is to use the declaration order to determine a match (first one to match wins), but that kind of goes against other overloads in D.

I thought of a number of possibilities, neither was good enough. I decided this is a small annoyance I'll need to live with. Andrei
Dec 14 2010
parent Max Samukha <spambox d-coding.com> writes:
On 12/14/2010 06:09 PM, Andrei Alexandrescu wrote:
 On 12/14/10 10:08 AM, Steven Schveighoffer wrote:
 On Tue, 14 Dec 2010 10:47:05 -0500, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 On 12/14/10 7:33 AM, biozic wrote:

 I have a question about this and some pieces of code in the standard
 library, notably std.algorithm: some of the templated functions use
 template constraints even when no template overloading is taking place.
 Wouldn't some static asserts help print more accurate messages when
 these functions are misused?

 Nicolas

The intent is to allow other code to define functions such as e.g. "map" or "sort". Generally any generic function should exclude via a constraint the inputs it can't work on. That way no generic function chews off more than it can bite.

Redirected from another thread. Having written a few of these functions with template constraints, I wondered if there was ever any discussion/agreement on reducing verbosity when specializing template constraints? For instance, if you want two overloads of a template, one which accepts types A and one which accepts types B where B implicitly converts to A (i.e. a specialization), you need to explicitly reject B's when defining the overload for A's. For example: void foo(R)(R r) if(isRandomAccessRange!R) {...} void foo(R)(R r) if(isInputRange!R && !isRandomAccessRange!R) {...} It seems redundant to specify !isRandomAccessRange!R in the second overload, but the compiler will complain otherwise. What sucks about this is the 'definition' of the first overload is partially in the second. That is, you don't really need that clause in the second overload unless you define the first one. Not only that, but it makes the template constraints grow in complexity quite quickly. Just look at a sample function in std.array that handles 'the default' case: void popFront(A)(ref A a) if(!isNarrowString!A && isDynamicArray!A && isMutable!A && !is(A == void[])) Any idea how this can be 'solved' or do we need to continue doing things like this? My naive instinct is to use the declaration order to determine a match (first one to match wins), but that kind of goes against other overloads in D.

I thought of a number of possibilities, neither was good enough. I decided this is a small annoyance I'll need to live with. Andrei

Should we think about making constrained templates more specialized than unconstrained? For example, the use case when I need one or more constrainted templates and an unconstrained catch-the-rest template pops up quite often. Consider the current behavior: void foo(T : int)(T x) { } void foo(T)(T x) // catches types not implicitly castable to int { } foo(1); // ok But: void foo(T)(T x) if (is(T : int)) { } void foo(T)(T x) { } foo(1); Error: template test.foo(T) if (is(T == int)) foo(T) if (is(T == int)) matches more than one template declaration, test.d(7):foo(T) if (is(T == int)) and test.d(11):foo(T) Also note that the compiler doesn't detect the ambiguity if there are constraints in the template parameter list: void foo(T : int)(T x) { } void foo(T)(T x) if (is(T == int)) { } foo(1); // first template is instantiated Essentially, interactions between template parameter constraints and if-constraints are not specified. What do you think?
Dec 14 2010
prev sibling next sibling parent spir <denis.spir gmail.com> writes:
On Tue, 14 Dec 2010 11:08:04 -0500
"Steven Schveighoffer" <schveiguy yahoo.com> wrote:

 Having written a few of these functions with template constraints, I =20
 wondered if there was ever any discussion/agreement on reducing verbosity=

 when specializing template constraints?
=20
 For instance, if you want two overloads of a template, one which accepts =

 types A and one which accepts types B where B implicitly converts to A =20
 (i.e. a specialization), you need to explicitly reject B's when defining =

 the overload for A's.  For example:
=20
=20
 void foo(R)(R r) if(isRandomAccessRange!R) {...}
=20
 void foo(R)(R r) if(isInputRange!R && !isRandomAccessRange!R) {...}
=20
=20
 It seems redundant to specify !isRandomAccessRange!R in the second =20
 overload, but the compiler will complain otherwise.  What sucks about thi=

 is the 'definition' of the first overload is partially in the second.  =20
 That is, you don't really need that clause in the second overload unless =

 you define the first one.  Not only that, but it makes the template =20
 constraints grow in complexity quite quickly.  Just look at a sample =20
 function in std.array that handles 'the default' case:
=20
=20
 void popFront(A)(ref A a) if(!isNarrowString!A && isDynamicArray!A && =20
 isMutable!A && !is(A =3D=3D void[]))
=20
 Any idea how this can be 'solved' or do we need to continue doing things =

 like this?  My naive instinct is to use the declaration order to determin=

 a match (first one to match wins), but that kind of goes against other =20
 overloads in D.

Another issue: it is rather bug prone: if you introduce another specialised= case, then it must be added to the series of negative constraints for the = default case. And, in case this new specialisation is (partially) orthogona= l, it must also be added as negative constraint to same or all other specia= lisations... I seems to me D already all syntactic & semantic elements to cleanly expres= s template constraints. I do not not understand why we do not reuse interfa= ces. Interfaces, I guess, allow clearly defining what is now implemented by= ad hoc template check functions like isRandomAccessRange(T), and/or with e= soteric uses of is(). Let us say we also reuse ':' as constraint-check operator. void f(T) () if (T:X) would mean: * if X is a type ~ T 'is' X ~ or T inherits X * if X is an interface ~ T explicitely implements X (its definition starts with "struct/type T= : X") ~ T factually implements X interface Writable { string toString (); } ... string listText (T) (T[] elements, string sep, string lDelim, string rDe= lim) if (T : Writable) { // returns eg "(e1 e2 e3...)" } The notion of "factual implementation" is analog to duck typing, except it = is a static, compile-time, fact. This does not solve the above-mentioned is= sue, but allows expressing it more clearly: void foo(R)(R r) if (R : InputRange && R !: RandomAccessRange) {...} To fully solve the issue, we should have a way to express "non-implementati= on" in interfaces, for instance reuse constraints(!): interface StrictInputRange (T) : InputRange if (T !: RandomAccessRange && T !: ForwardRange ...) {} Anyway, the result is: void foo(R)(R r) if (R : StrictInputRange) {...} Denis -- -- -- -- -- -- -- vit esse estrany =E2=98=A3 spir.wikidot.com
Dec 14 2010
prev sibling next sibling parent reply so <so so.do> writes:
 Any idea how this can be 'solved' or do we need to continue doing things  
 like this?  My naive instinct is to use the declaration order to  
 determine a match (first one to match wins), but that kind of goes  
 against other overloads in D.

One big plus of current solution is that everything you need for that specialization lies in the signature. I can't see another approach that scales better at this. If scaling for constraints is something important. In: void foo(R)(R r) if(isRandomAccessRange!R) {...} void foo(R)(R r) if(isInputRange!R && !isRandomAccessRange!R) {...} void foo(R)(R r) if(isInputRange!R && !isRandomAccessRange!R) {...} We can deduce it is equal to: void foo(R)(R r) if(isInputRange!R) {...} For single/two constraints it isn't hard, when things get ugly determining what means what is not quite easy as far as i can see. Just consider if your first specialization had two constraints. -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 14 2010
next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 14 Dec 2010 13:51:50 -0500, so <so so.do> wrote:

 Any idea how this can be 'solved' or do we need to continue doing  
 things like this?  My naive instinct is to use the declaration order to  
 determine a match (first one to match wins), but that kind of goes  
 against other overloads in D.

One big plus of current solution is that everything you need for that specialization lies in the signature. I can't see another approach that scales better at this. If scaling for constraints is something important. In: void foo(R)(R r) if(isRandomAccessRange!R) {...} void foo(R)(R r) if(isInputRange!R && !isRandomAccessRange!R) {...} void foo(R)(R r) if(isInputRange!R && !isRandomAccessRange!R) {...} We can deduce it is equal to: void foo(R)(R r) if(isInputRange!R) {...} For single/two constraints it isn't hard, when things get ugly determining what means what is not quite easy as far as i can see. Just consider if your first specialization had two constraints.

On the contrary, I think it scales very poorly from a function-writer point of view. Let's say I wanted to add a version that implements a specialization for forward ranges, I now have to modify the constraint on the one that does input ranges. This is opposite to how derived classes or specialized overloads work, I just define the specialization, and if it doesn't match it falls back to the default. Imagine now if I wanted to define a foo that worked only on my specific range, I now have to go back and modify the constraints of all the other functions. What if I don't have control over that module? -Steve
Dec 14 2010
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/14/10 1:43 PM, Steven Schveighoffer wrote:
 On Tue, 14 Dec 2010 13:51:50 -0500, so <so so.do> wrote:

 Any idea how this can be 'solved' or do we need to continue doing
 things like this? My naive instinct is to use the declaration order
 to determine a match (first one to match wins), but that kind of goes
 against other overloads in D.

One big plus of current solution is that everything you need for that specialization lies in the signature. I can't see another approach that scales better at this. If scaling for constraints is something important. In: void foo(R)(R r) if(isRandomAccessRange!R) {...} void foo(R)(R r) if(isInputRange!R && !isRandomAccessRange!R) {...} void foo(R)(R r) if(isInputRange!R && !isRandomAccessRange!R) {...} We can deduce it is equal to: void foo(R)(R r) if(isInputRange!R) {...} For single/two constraints it isn't hard, when things get ugly determining what means what is not quite easy as far as i can see. Just consider if your first specialization had two constraints.

On the contrary, I think it scales very poorly from a function-writer point of view. Let's say I wanted to add a version that implements a specialization for forward ranges, I now have to modify the constraint on the one that does input ranges. This is opposite to how derived classes or specialized overloads work, I just define the specialization, and if it doesn't match it falls back to the default. Imagine now if I wanted to define a foo that worked only on my specific range, I now have to go back and modify the constraints of all the other functions. What if I don't have control over that module?

It scales poorly on artificial examples. It scales well on real-world examples, because those usually have disjunctive constraints. Andrei
Dec 14 2010
prev sibling parent spir <denis.spir gmail.com> writes:
On Tue, 14 Dec 2010 13:57:43 -0600
Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:

 Imagine now if I wanted to define a foo that worked only on my specific
 range, I now have to go back and modify the constraints of all the other
 functions. What if I don't have control over that module? =20

It scales poorly on artificial examples. It scales well on real-world=20 examples, because those usually have disjunctive constraints.

Not sure of this. Look a the thread about "write, toString, formatValue & r= ange interface". It seems (but I may be wrong) that input range cases where= added to the (big) set of formatValue templates. If my reasoning is correc= t, this addition was not properly done, precisely negative constraints for = mutual exclusion are missing, which leads to 2-3 bugs. Denis -- -- -- -- -- -- -- vit esse estrany =E2=98=A3 spir.wikidot.com
Dec 15 2010
prev sibling next sibling parent reply Patrick Down <pat codemoon.com> writes:
== Quote from Steven Schveighoffer (schveiguy yahoo.com)'s article
Would it help to allow 'else' and 'else if' on the template constraints?

void foo(R)(R r) if(isRandomAccessRange!R)
{...}
else if(isInputRange!R)
{...}

This could basically be translated into two specializations like this:

void foo(R)(R r) if(isRandomAccessRange!R) {...}
void foo(R)(R r) if(isInputRange!R && !isRandomAccessRange!R) {...}
Dec 14 2010
next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 14 Dec 2010 15:35:30 -0500, Patrick Down <pat codemoon.com> wrote:

 == Quote from Steven Schveighoffer (schveiguy yahoo.com)'s article
 Would it help to allow 'else' and 'else if' on the template constraints?

 void foo(R)(R r) if(isRandomAccessRange!R)
 {...}
 else if(isInputRange!R)
 {...}

 This could basically be translated into two specializations like this:

 void foo(R)(R r) if(isRandomAccessRange!R) {...}
 void foo(R)(R r) if(isInputRange!R && !isRandomAccessRange!R) {...}

:O Wow, that's a great idea. Andrei? -Steve
Dec 14 2010
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/14/10 2:44 PM, Steven Schveighoffer wrote:
 On Tue, 14 Dec 2010 15:35:30 -0500, Patrick Down <pat codemoon.com> wrote:

 == Quote from Steven Schveighoffer (schveiguy yahoo.com)'s article
 Would it help to allow 'else' and 'else if' on the template constraints?

 void foo(R)(R r) if(isRandomAccessRange!R)
 {...}
 else if(isInputRange!R)
 {...}

 This could basically be translated into two specializations like this:

 void foo(R)(R r) if(isRandomAccessRange!R) {...}
 void foo(R)(R r) if(isInputRange!R && !isRandomAccessRange!R) {...}

:O Wow, that's a great idea. Andrei?

I'll cautiously say "looks okay", but in terms of allowing us to do great things, it is way below many other things. Consider for example the annoying limitation with the eponymous trick: you can't define any other symbols. That is unnecessary and causes a lot of code and name bloat. I'd much prefer that issue were fixed instead of the above. Andrei
Dec 14 2010
prev sibling next sibling parent so <so so.do> writes:
It is exactly your proposal (first one to match wins), with uglier syntax  
:D
Would it even fulfill your requirements? For example "What if I don't have  
control over that module?"
This one would make it impossible.
Dec 14 2010
prev sibling next sibling parent so <so so.do> writes:
s/exactly/essentially/
Dec 14 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 14 Dec 2010 16:03:18 -0500, so <so so.do> wrote:

 It is exactly your proposal (first one to match wins), with uglier  
 syntax :D

Not exactly. It fits within the syntax of D (if-else), and order of evaluation is explicit, whereas one might expect with my original proposal that order does not matter. There is no disputing which template should be instantiated. But yes, it is the same premise. And I don't agree the syntax is uglier. Maintenance would be easier (only one signature need be modified). Also only need to document in one spot.
 Would it even fulfill your requirements? For example "What if I don't  
 have control over that module?"
 This one would make it impossible.

It doesn't fulfill that requirement, no. But it gets us less verbose definition where you do control the module. Solving some of the requirements without solving them all is allowed. Actually, now that I think about it, that kind of fits D as well. Overload resolution is not done across modules. -Steve
Dec 14 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 14 Dec 2010 16:35:49 -0500, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 On 12/14/10 2:44 PM, Steven Schveighoffer wrote:
 On Tue, 14 Dec 2010 15:35:30 -0500, Patrick Down <pat codemoon.com>  
 wrote:

 == Quote from Steven Schveighoffer (schveiguy yahoo.com)'s article
 Would it help to allow 'else' and 'else if' on the template  
 constraints?

 void foo(R)(R r) if(isRandomAccessRange!R)
 {...}
 else if(isInputRange!R)
 {...}

 This could basically be translated into two specializations like this:

 void foo(R)(R r) if(isRandomAccessRange!R) {...}
 void foo(R)(R r) if(isInputRange!R && !isRandomAccessRange!R) {...}

:O Wow, that's a great idea. Andrei?

I'll cautiously say "looks okay", but in terms of allowing us to do great things, it is way below many other things. Consider for example the annoying limitation with the eponymous trick: you can't define any other symbols. That is unnecessary and causes a lot of code and name bloat. I'd much prefer that issue were fixed instead of the above.

I agree. My original question was just "have we thought about this", not "this must be solved immediately!" If this was implemented, it would be backwards-compatible anyways, so it's not pressing. -Steve
Dec 14 2010
prev sibling next sibling parent reply "Daniel Murphy" <yebblies nospamgmail.com> writes:
"Patrick Down" <pat codemoon.com> wrote in message 
news:ie8kei$1gdg$1 digitalmars.com...
 Would it help to allow 'else' and 'else if' on the template constraints?

 void foo(R)(R r) if(isRandomAccessRange!R)
 {...}
 else if(isInputRange!R)
 {...}

 This could basically be translated into two specializations like this:

 void foo(R)(R r) if(isRandomAccessRange!R) {...}
 void foo(R)(R r) if(isInputRange!R && !isRandomAccessRange!R) {...}

You can sorta already do this... template foo(R) { static if (isRandomAccessRange!R) foo(R r) {} else static if (isInputRange!R) foo(R r) {} else static assert(0, "R must be at least an input range"); }
Dec 15 2010
parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, December 15, 2010 05:16:29 Steven Schveighoffer wrote:
 On Wed, 15 Dec 2010 04:34:53 -0500, Daniel Murphy
 
 <yebblies nospamgmail.com> wrote:
 "Patrick Down" <pat codemoon.com> wrote in message
 news:ie8kei$1gdg$1 digitalmars.com...
 
 Would it help to allow 'else' and 'else if' on the template constraints?
 
 void foo(R)(R r) if(isRandomAccessRange!R)
 {...}
 else if(isInputRange!R)
 {...}
 
 This could basically be translated into two specializations like this:
 
 void foo(R)(R r) if(isRandomAccessRange!R) {...}
 void foo(R)(R r) if(isInputRange!R && !isRandomAccessRange!R) {...}

You can sorta already do this... template foo(R) { static if (isRandomAccessRange!R) foo(R r) {} else static if (isInputRange!R) foo(R r) {} else static assert(0, "R must be at least an input range"); }

The compiler doesn't treat this the same: 1. no ifti 2. if no branches accept the R type, it tries another template.

By the way, what does IFTI stand for? - Jonathan M Davis
Dec 15 2010
parent David Nadlinger <see klickverbot.at> writes:
On 12/15/10 7:57 PM, Jonathan M Davis wrote:
 By the way, what does IFTI stand for?

Implicit Function Template Instantiation, which allows you to do »void foo(T)(T bar) {}; foo(5);« without manually specifying the template parameter. David
Dec 15 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 15 Dec 2010 04:34:53 -0500, Daniel Murphy  
<yebblies nospamgmail.com> wrote:

 "Patrick Down" <pat codemoon.com> wrote in message
 news:ie8kei$1gdg$1 digitalmars.com...
 Would it help to allow 'else' and 'else if' on the template constraints?

 void foo(R)(R r) if(isRandomAccessRange!R)
 {...}
 else if(isInputRange!R)
 {...}

 This could basically be translated into two specializations like this:

 void foo(R)(R r) if(isRandomAccessRange!R) {...}
 void foo(R)(R r) if(isInputRange!R && !isRandomAccessRange!R) {...}

You can sorta already do this... template foo(R) { static if (isRandomAccessRange!R) foo(R r) {} else static if (isInputRange!R) foo(R r) {} else static assert(0, "R must be at least an input range"); }

The compiler doesn't treat this the same: 1. no ifti 2. if no branches accept the R type, it tries another template. -Steve
Dec 15 2010
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 15 Dec 2010 08:16:29 -0500, Steven Schveighoffer  
<schveiguy yahoo.com> wrote:

 On Wed, 15 Dec 2010 04:34:53 -0500, Daniel Murphy  
 <yebblies nospamgmail.com> wrote:

 "Patrick Down" <pat codemoon.com> wrote in message
 news:ie8kei$1gdg$1 digitalmars.com...
 Would it help to allow 'else' and 'else if' on the template  
 constraints?

 void foo(R)(R r) if(isRandomAccessRange!R)
 {...}
 else if(isInputRange!R)
 {...}

 This could basically be translated into two specializations like this:

 void foo(R)(R r) if(isRandomAccessRange!R) {...}
 void foo(R)(R r) if(isInputRange!R && !isRandomAccessRange!R) {...}

You can sorta already do this... template foo(R) { static if (isRandomAccessRange!R) foo(R r) {} else static if (isInputRange!R) foo(R r) {} else static assert(0, "R must be at least an input range"); }

The compiler doesn't treat this the same: 1. no ifti 2. if no branches accept the R type, it tries another template.

that #2 should be it *does not try* another template. Template constraints do try it. -Steve
Dec 15 2010
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
Steven Schveighoffer wrote:
 Any idea how this can be 'solved' or do we need to continue doing things 
 like this?  My naive instinct is to use the declaration order to 
 determine a match (first one to match wins), but that kind of goes 
 against other overloads in D.

Yeah, I'd be extremely reluctant to change from a best match to a first match. One reason is that first match cannot deal well with partial ordering, as it by definition requires a total ordering. Another reason is D's attempt to get away from C/C++'s declaration ordering dependencies.
Dec 14 2010