www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - user defined implicit casts

reply Ender KaShae <astrothayne gmail.com> writes:
I have become aware that in D it is possible to create wrapper classes that can
intermingle with primitave types for example:

/**
 * wrappers for all the basic types (and derived types) of D
 * note: these wrappers are mutable
 */
module dragon.math.basictypes;
import dragon.math.numbers;
import std.math;
import std.string;

/// wrapper for byte
class Byte: numeric!(Byte){
	private byte value; /// value of instance
	public:
		this(byte b = 0){ value = b;}	///Constructor
		byte Value() {return value;}	///Returns: value of instance
		void Value(byte b) {value = b;}	///Params: b = new value
		byte opCast() {return value;} /// cast overload
		char[] toString() { return std.string.toString(value);}
		uint toHash() {return abs(value);}

		// binary operator TODO add documentation
		Byte opAdd(Byte b) { return new Byte(value + b.value);}
		Byte opAdd(byte b) { return new Byte(value + b);}
		Byte opSub(Byte b) { return new Byte(value - b.value);}
		Byte opSub(byte b) { return new Byte(value - b);}
		Byte opSub_r(byte b) { return new Byte(b - value);}
		Byte opMul(Byte b) { return new Byte(value * b.value);}
		Byte opMul(byte b) { return new Byte(value * b);}
		Byte opDiv(Byte b) { return new Byte(value / b.value);}
		Byte opDiv(byte b) { return new Byte(value / b);}
		Byte opDiv_r(byte b) {return new Byte(b / value);}
		Byte opMod(Byte b) {return new Byte(value % b.value);}
		Byte opMod(byte b) {return new Byte(value % b);}
		Byte opMod_r(byte b) { return new Byte(b % value);}
		Byte opAnd(Byte b) { return new Byte(value & b.value);}
		Byte opAnd(byte b) { return new Byte(value & b);}
		Byte opOr(Byte b) { return new Byte(value | b.value);}
		Byte opOr(byte b) { return new Byte(value | b);}
		Byte opXor(Byte b) { return new Byte(value ^ b.value);}
		Byte opXor(byte b) { return new Byte(value ^ b);}
		Byte opShl(int i) { return new Byte(value << i);}
		long opShl_r(long l) { return l << value;}
		Byte opShr(int i) {return new Byte(value >> i);}
		long opShr_r(long l) { return l >> value;}
		Byte opUShr(int i) {return new Byte(value >>> i);}
		long opUShr(long l) {return l >>> value;}
		//comparison operators
		int opEquals(Object o) {
			if(o is Byte) {
				Byte b = cast(Byte)o;
				return value == b.value;
			}
			return o == value;
		}
		int opEquals(long l) {
			return l = value;
		}
		int opCmp(Object o) {
			if(o is Byte) {
				Byte b = cast(Byte)o;
				if(value > b.value)return 1;
				if(value < b.value)return -1;
				return 0;
			}
			return o.opCmp(value);
		}
		int opCmp(long l) {
			if(value < l)return -1;
			if(value > l)return 1;
			return 0;
		}
		//assignment operators
		Byte opAssign(byte b) {
			value = b;
			return this;
		}
		Byte opAddAssign(Byte b) {
			value += b.value;
			return this;
		}
		Byte opAddAssign(byte b) {
			value += b;
			return this;
		}
		Byte opSubAssign(Byte b) {
			value -= b.value;
			return this;
		}
		Byte opSubAssign(byte b) {
			value -= b;
			return this;
		}
		Byte opMulAssign(Byte b) {
			value *= b.value;
			return this;
		}
		Byte opMulAssign(byte b) {
			value *= b;
			return this;
		}
		Byte opDivAssign(Byte b) {
			value /= b.value;
			return this;
		}
		Byte opDivAssign(byte b) {
			value /= b;
			return this;
		}
		Byte opModAssign(Byte b) {
			value %= b.value;
			return this;
		}
		Byte opModAssign(byte b) {
			value %= b;
			return this;
		}
		Byte opAndAssign(Byte b) {
			value &= b.value;
			return this;
		}
		Byte opAndAssign(byte b) {
			value &= b;
			return this;
		}
		Byte opOrAssign(Byte b) {
			value |= b.value;
			return this;
		}
		Byte opOrAssign(byte b) {
			value |= b;
			return this;
		}
		Byte opXorAssign(Byte b) {
			value ^= b.value;
			return this;
		}
		Byte opXorAssign(byte b) {
			value ^= b;
			return this;
		}
		Byte opShlAssign(Byte b) {
			value <<= b.value;
			return this;
		}
		Byte opShlAssign(byte b) {
			value <<= b;
			return this;
		}
		Byte opShrAssign(Byte b) {
			value >>= b.value;
			return this;
		}
		Byte opShrAssign(byte b) {
			value >>= b;
			return this;
		}
		Byte opUShrAssign(Byte b) {
			value >>>= b.value;
			return this;
		}
		Byte opUShrAssign(byte b) {
			value >>>= b;
			return this;
		}

		// unary operators TODO add documentation
		Byte opNeg() {return new Byte(-value);}
		Byte opPos() {return new Byte(+value);}
		Byte opCom() {return new Bye(~value);}
		Byte opPostInc() {
			scope temp = value;
			value++;
			return new Byte(temp);
		}
		Byte opPostDec() {
			scope temp = value;
			value--;
			return new Byte(temp);
		}
}

now the problem is a lack of implicit casting for user defined types the Byte
cannot be implicitly cast to byte, int, long etc. even though such a cast would
make sense.  If implicit casting were allowed then a library could be fashioned
with wrapper for all of the basic types and some less basic types such as cint,
iint, fraction, radical, ifraction, cfraction, etc.  

my suggested syntax for overloading implicit casts is:

implicit type1 cast(type2 var);

where type1 is the type casting to and type2 is the type casting from
this syntax would allow non-member functions to be used, so that implicit casts
can be overloaded in specific contexts 
example:
implicit int cast(long var){ return cast(int)var;}
when you know that longs will never exceed the bound of ints
Jul 27 2007
next sibling parent reply Tristam MacDonald <swiftcoder gmail.com> writes:
While I would like to see this, I wopuld like to see a logical extension of the
last part even more: non-member operator overloads. This is an incredibly neat
feature from C++ that was lost somewhere along the way, but I don't know if it
was intentional or not...

Ender KaShae Wrote:
 I have become aware that in D it is possible to create wrapper classes that
can intermingle with primitave types for example:
 
 /**
  * wrappers for all the basic types (and derived types) of D
  * note: these wrappers are mutable
  */
 module dragon.math.basictypes;
 import dragon.math.numbers;
 import std.math;
 import std.string;
 
 /// wrapper for byte
 class Byte: numeric!(Byte){
 	private byte value; /// value of instance
 	public:
 		this(byte b = 0){ value = b;}	///Constructor
 		byte Value() {return value;}	///Returns: value of instance
 		void Value(byte b) {value = b;}	///Params: b = new value
 		byte opCast() {return value;} /// cast overload
 		char[] toString() { return std.string.toString(value);}
 		uint toHash() {return abs(value);}
 
 		// binary operator TODO add documentation
 		Byte opAdd(Byte b) { return new Byte(value + b.value);}
 		Byte opAdd(byte b) { return new Byte(value + b);}
 		Byte opSub(Byte b) { return new Byte(value - b.value);}
 		Byte opSub(byte b) { return new Byte(value - b);}
 		Byte opSub_r(byte b) { return new Byte(b - value);}
 		Byte opMul(Byte b) { return new Byte(value * b.value);}
 		Byte opMul(byte b) { return new Byte(value * b);}
 		Byte opDiv(Byte b) { return new Byte(value / b.value);}
 		Byte opDiv(byte b) { return new Byte(value / b);}
 		Byte opDiv_r(byte b) {return new Byte(b / value);}
 		Byte opMod(Byte b) {return new Byte(value % b.value);}
 		Byte opMod(byte b) {return new Byte(value % b);}
 		Byte opMod_r(byte b) { return new Byte(b % value);}
 		Byte opAnd(Byte b) { return new Byte(value & b.value);}
 		Byte opAnd(byte b) { return new Byte(value & b);}
 		Byte opOr(Byte b) { return new Byte(value | b.value);}
 		Byte opOr(byte b) { return new Byte(value | b);}
 		Byte opXor(Byte b) { return new Byte(value ^ b.value);}
 		Byte opXor(byte b) { return new Byte(value ^ b);}
 		Byte opShl(int i) { return new Byte(value << i);}
 		long opShl_r(long l) { return l << value;}
 		Byte opShr(int i) {return new Byte(value >> i);}
 		long opShr_r(long l) { return l >> value;}
 		Byte opUShr(int i) {return new Byte(value >>> i);}
 		long opUShr(long l) {return l >>> value;}
 		//comparison operators
 		int opEquals(Object o) {
 			if(o is Byte) {
 				Byte b = cast(Byte)o;
 				return value == b.value;
 			}
 			return o == value;
 		}
 		int opEquals(long l) {
 			return l = value;
 		}
 		int opCmp(Object o) {
 			if(o is Byte) {
 				Byte b = cast(Byte)o;
 				if(value > b.value)return 1;
 				if(value < b.value)return -1;
 				return 0;
 			}
 			return o.opCmp(value);
 		}
 		int opCmp(long l) {
 			if(value < l)return -1;
 			if(value > l)return 1;
 			return 0;
 		}
 		//assignment operators
 		Byte opAssign(byte b) {
 			value = b;
 			return this;
 		}
 		Byte opAddAssign(Byte b) {
 			value += b.value;
 			return this;
 		}
 		Byte opAddAssign(byte b) {
 			value += b;
 			return this;
 		}
 		Byte opSubAssign(Byte b) {
 			value -= b.value;
 			return this;
 		}
 		Byte opSubAssign(byte b) {
 			value -= b;
 			return this;
 		}
 		Byte opMulAssign(Byte b) {
 			value *= b.value;
 			return this;
 		}
 		Byte opMulAssign(byte b) {
 			value *= b;
 			return this;
 		}
 		Byte opDivAssign(Byte b) {
 			value /= b.value;
 			return this;
 		}
 		Byte opDivAssign(byte b) {
 			value /= b;
 			return this;
 		}
 		Byte opModAssign(Byte b) {
 			value %= b.value;
 			return this;
 		}
 		Byte opModAssign(byte b) {
 			value %= b;
 			return this;
 		}
 		Byte opAndAssign(Byte b) {
 			value &= b.value;
 			return this;
 		}
 		Byte opAndAssign(byte b) {
 			value &= b;
 			return this;
 		}
 		Byte opOrAssign(Byte b) {
 			value |= b.value;
 			return this;
 		}
 		Byte opOrAssign(byte b) {
 			value |= b;
 			return this;
 		}
 		Byte opXorAssign(Byte b) {
 			value ^= b.value;
 			return this;
 		}
 		Byte opXorAssign(byte b) {
 			value ^= b;
 			return this;
 		}
 		Byte opShlAssign(Byte b) {
 			value <<= b.value;
 			return this;
 		}
 		Byte opShlAssign(byte b) {
 			value <<= b;
 			return this;
 		}
 		Byte opShrAssign(Byte b) {
 			value >>= b.value;
 			return this;
 		}
 		Byte opShrAssign(byte b) {
 			value >>= b;
 			return this;
 		}
 		Byte opUShrAssign(Byte b) {
 			value >>>= b.value;
 			return this;
 		}
 		Byte opUShrAssign(byte b) {
 			value >>>= b;
 			return this;
 		}
 
 		// unary operators TODO add documentation
 		Byte opNeg() {return new Byte(-value);}
 		Byte opPos() {return new Byte(+value);}
 		Byte opCom() {return new Bye(~value);}
 		Byte opPostInc() {
 			scope temp = value;
 			value++;
 			return new Byte(temp);
 		}
 		Byte opPostDec() {
 			scope temp = value;
 			value--;
 			return new Byte(temp);
 		}
 }
 
 now the problem is a lack of implicit casting for user defined types the Byte
cannot be implicitly cast to byte, int, long etc. even though such a cast would
make sense.  If implicit casting were allowed then a library could be fashioned
with wrapper for all of the basic types and some less basic types such as cint,
iint, fraction, radical, ifraction, cfraction, etc.  
 
 my suggested syntax for overloading implicit casts is:
 
 implicit type1 cast(type2 var);
 
 where type1 is the type casting to and type2 is the type casting from
 this syntax would allow non-member functions to be used, so that implicit
casts can be overloaded in specific contexts 
 example:
 implicit int cast(long var){ return cast(int)var;}
 when you know that longs will never exceed the bound of ints

Jul 27 2007
parent "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Tristam MacDonald" <swiftcoder gmail.com> wrote in message 
news:f8dndh$1c8l$1 digitalmars.com...
 While I would like to see this, I wopuld like to see a logical extension 
 of the last part even more: non-member operator overloads. This is an 
 incredibly neat feature from C++ that was lost somewhere along the way, 
 but I don't know if it was intentional or not...

It was intentional. The problem with global or static (as in C#) operator overloads is that they cannot be virtual, and so you end up writing the non-virtual operator overloads which simply call a virtual implementation method on the object(s), which is pointless -- just make the overload virtual in the first place, and it works fine. How would non-member operator overloads solve this problem anyway?
Jul 28 2007
prev sibling parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
Ender KaShae wrote:
 ...
 now the problem is a lack of implicit casting for user defined types the Byte
cannot be implicitly cast to byte, int, long etc. even though such a cast would
make sense.  If implicit casting were allowed then a library could be fashioned
with wrapper for all of the basic types and some less basic types such as cint,
iint, fraction, radical, ifraction, cfraction, etc.  
 
 my suggested syntax for overloading implicit casts is:
 
 implicit type1 cast(type2 var);
 where type1 is the type casting to and type2 is the type casting from

Eww, ick. First of all, you're overloading on return type, *and* you've introduced a new keyword. Sorry :) D already allows you to define an explicit casting overload like so: class Foo { type1 opCast() { ... } } What has been suggested a few times is to make a new operator overload that passes the result out using an out argument instead, which you *can* overload on: class Foo { void opImplicitCast(out type1) { ... } } On the issue of using a global function (as opposed to a member function), I'd rather get general type extensions, which would also provide this as a sort of side effect: bool isFoo(Object this) { return (cast(Foo)this) !is null; } auto bar = (new Foo).isFoo;
 this syntax would allow non-member functions to be used, so that implicit
casts can be overloaded in specific contexts 
 example:
 implicit int cast(long var){ return cast(int)var;}
 when you know that longs will never exceed the bound of ints

Ok, there's something very wrong about this. Altering how casts happen in particular contexts just strikes me as a very bad idea. The problem is that someone reading your code will need to go over it with a fine tooth comb to work out what casts are *actually* doing at any given line. That said, I *would* like to see implicit casts added to the language. -- Daniel
Jul 27 2007
parent Ender KaShae <astrothayne gmail.com> writes:
Daniel Keep Wrote:

 
uggested syntax for overloading implicit casts is:
 
 implicit type1 cast(type2 var);
 where type1 is the type casting to and type2 is the type casting from

Eww, ick. First of all, you're overloading on return type, *and* you've introduced a new keyword. Sorry :) D already allows you to define an explicit casting overload like so: class Foo { type1 opCast() { ... } } What has been suggested a few times is to make a new operator overload that passes the result out using an out argument instead, which you *can* overload on: class Foo { void opImplicitCast(out type1) { ... } }

I like that much better than my suggestion, it's a lot cleaner
 On the issue of using a global function (as opposed to a member
 function), I'd rather get general type extensions, which would also
 provide this as a sort of side effect:
 
 bool isFoo(Object this)
 {
     return (cast(Foo)this) !is null;
 }
 
 auto bar = (new Foo).isFoo;
 

 
 That said, I *would* like to see implicit casts added to the language.
 
 	-- Daniel

Jul 28 2007