www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - An idiom for disabling implicit conversions

reply Don <nospam nospam.com> writes:
I've found a pretty cool idiom for disabling pesky implicit conversion.

This is a situation that I've encountered with BigInt; but I think it's 
a general problem.
I have an "add integer" operation. Let's call it:
void add( x ), where x is a built-in integral type.

If I define add(long x), everything works great -- it can be called with 
x as a byte, ubyte, short, ushort, int, uint, or long.
Well, almost. Unfortunately, it doesn't work for ulong: add(ulong.max) 
will either fail to compile, or get implicitly cast to add(-1)!!

You can fix this by defining add(ulong x) as a special case. Now x can 
be long or ulong, and it works. Great!
Problem is, now if x is an integer, the compiler complains that it's 
ambiguous -- should it call the long or the ulong version? Bummer!

You could solve that by creating int, uint, short, ushort,... functions. 
Eight in total, but seven of them are identical. Massive source code 
duplication. So use a template:

void add(T)(T x)
{
   // everything except ulong
}

void add(T : ulong)(T x)
{
}

Problem solved! Well, not quite. Now the source code duplication is 
gone, but you still have template bloat: there are still 8 functions in 
the executable. Then consider
void evenworse(x, y)
where both x AND y need special treatment if they are ulong. 8*8 = 64 
functions go into the executable, but only 4 of them are different! Ouch.

If only there was a way to disable implicit conversions...

void add()(long x)
{
     assert(x == 7);
}

void add(Tulong)(Tulong x)   if ( is(Tulong == ulong) )
{
     assert(x == 6);
}

void main()
{
     add(7L);
     add(6UL);
     add(7); // Look ma, no conflicts! No bloat!
}

Template constraints are seriously cool.
Mar 19 2010
next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Don:

Do you know why this semantics:

 void add(Tulong)(Tulong x)   if ( is(Tulong == ulong) )

Is different from this one? void add(Tulong:ulong)(Tulong x) { This page says: http://www.digitalmars.com/d/2.0/templates-revisited.html T:int, // T must be int type So it seems the same as the version with the template constraint. Can't the D2 semantics of (Tulong:ulong) be redefined to become the same of the version with template constraint if(is(Tulong==ulong)) to remove this special case from D?
 Template constraints are seriously cool.

I agree :-) And Andrei has used them a lot in Phobos2. Thank you for your trick (but but the idea is to have a language that needs as few tricks as possible). Bye, bearophile
Mar 19 2010
parent reply Don <nospam nospam.com> writes:
bearophile wrote:
 Don:
 
 Do you know why this semantics:
 
 void add(Tulong)(Tulong x)   if ( is(Tulong == ulong) )

Is different from this one? void add(Tulong:ulong)(Tulong x) { This page says: http://www.digitalmars.com/d/2.0/templates-revisited.html T:int, // T must be int type So it seems the same as the version with the template constraint.

It isn't. It's the same as: add(ulong x) which leads to an ambiguity with add(long x). Template constraints are considerably more powerful than template specialisation.
 
 Can't the D2 semantics of (Tulong:ulong) be redefined to become the same of
the version with template constraint if(is(Tulong==ulong)) to remove this
special case from D?

Not without loss of capability.
Mar 19 2010
parent bearophile <bearophileHUGS lycos.com> writes:
Don:
 Not without loss of capability.

So this means "all separately instantiated add() functions with T that the compiler can automatically cast to uint": void add(T:ulong)(T x) { So it's something very different from the template constraint, that just accepts/refuses to see the template according to the if clause. I have programmed D1 for years thinking add(T:ulong) means something different, a single instantiation with ulong. I was wrong. Thank you, as usual, bearophile
Mar 19 2010
prev sibling parent Justin Johansson <no spam.com> writes:
The problem is that D does not have any formal semantics for its
informal type system (this, of course, being a tautological statement).

The discovery of any "cool idiom" for disabling "pesky implicit
conversion" is tantamount to engaging the same sorts of hacks
that web programmers do with CSS (Cascading Style Sheets). Need
I elaborate?

People might scoff as they surely will but to even begin to
understand formalism in data types this would be a good
place to start ...

W3C XML Schema Definition Language (XSD) 1.1 Part 2: Datatypes

http://www.w3.org/TR/2009/WD-xmlschema11-2-20091203/

Editors (Version 1.1):
    David Peterson, invited expert (SGMLWorks!) <davep iit.edu>
    Shudi (Sandy) Gao &#39640;&#27530;&#38237;, IBM <sandygao ca.ibm.com>
    Ashok Malhotra, Oracle Corporation <ashokmalhotra alum.mit.edu>
    C. M. Sperberg-McQueen, Black Mesa Technologies LLC
<cmsmcq blackmesatech.com>
    Henry S. Thompson, University of Edinburgh <ht inf.ed.ac.uk>

Regards
Justin Johansson


Don Wrote:

 I've found a pretty cool idiom for disabling pesky implicit conversion.
 
 This is a situation that I've encountered with BigInt; but I think it's 
 a general problem.
 I have an "add integer" operation. Let's call it:
 void add( x ), where x is a built-in integral type.
 
 If I define add(long x), everything works great -- it can be called with 
 x as a byte, ubyte, short, ushort, int, uint, or long.
 Well, almost. Unfortunately, it doesn't work for ulong: add(ulong.max) 
 will either fail to compile, or get implicitly cast to add(-1)!!
 
 You can fix this by defining add(ulong x) as a special case. Now x can 
 be long or ulong, and it works. Great!
 Problem is, now if x is an integer, the compiler complains that it's 
 ambiguous -- should it call the long or the ulong version? Bummer!
 
 You could solve that by creating int, uint, short, ushort,... functions. 
 Eight in total, but seven of them are identical. Massive source code 
 duplication. So use a template:
 
 void add(T)(T x)
 {
    // everything except ulong
 }
 
 void add(T : ulong)(T x)
 {
 }
 
 Problem solved! Well, not quite. Now the source code duplication is 
 gone, but you still have template bloat: there are still 8 functions in 
 the executable. Then consider
 void evenworse(x, y)
 where both x AND y need special treatment if they are ulong. 8*8 = 64 
 functions go into the executable, but only 4 of them are different! Ouch.
 
 If only there was a way to disable implicit conversions...
 
 void add()(long x)
 {
      assert(x == 7);
 }
 
 void add(Tulong)(Tulong x)   if ( is(Tulong == ulong) )
 {
      assert(x == 6);
 }
 
 void main()
 {
      add(7L);
      add(6UL);
      add(7); // Look ma, no conflicts! No bloat!
 }
 
 Template constraints are seriously cool.

Mar 19 2010