digitalmars.D.learn - TDPL: Operator Overloading
- Andrej Mitrovic (63/63) Aug 24 2010 This is a shortened version of some operator overloading code from page ...
- Yao G. (6/55) Aug 24 2010 That's a bug. The return value should be CheckedInt(result);
- Andrej Mitrovic (5/8) Aug 24 2010 I'll add that to the errata.
- Yao G. (15/21) Aug 24 2010 Sorry. My mistake, I probably misread.
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (26/40) Aug 24 2010 It is the same in C++: the name of the template is equivalent to the
- bearophile (5/11) Aug 24 2010 I don't like this "feature" much, it has caused me troubles:
- Steven Schveighoffer (6/12) Aug 25 2010 Inside a template instantiation, the template name without template
- bearophile (4/8) Aug 25 2010 And causes some troubles :-) It's a barter, and I am not sure it's a goo...
- Steven Schveighoffer (16/23) Aug 25 2010 What trouble? I've already stated in the bug report how there's not
- Andrej Mitrovic (39/57) Aug 25 2010 Ok I think I am kind of getting this. The template name inside a templat...
- Steven Schveighoffer (18/59) Aug 25 2010 A struct is a value type, so you are making a copy regardless. Your
- Andrej Mitrovic (2/4) Aug 25 2010
- Steven Schveighoffer (33/38) Aug 25 2010 BTW, a unit test model I use in dcollections quite a bit is putting unit...
- Andrej Mitrovic (21/28) Aug 25 2010 Interesting. :)
- Andrej Mitrovic (3/4) Aug 25 2010 Although that might not work with floats since that would call foo with ...
- Andrej Mitrovic (2/10) Aug 25 2010
- Philippe Sigaud (12/16) Aug 26 2010 It reminds me a bit of Haskell's QuickCheck library, used to test code.
This is a shortened version of some operator overloading code from page 372 (although some code is from pages before it), sorry for the long post: module binary_ops; import std.stdio : writeln; import std.traits; import std.exception; unittest { auto foo = CheckedInt!(int)(5); auto bar = CheckedInt!(int)(5); foo = foo + bar; writeln(foo.Value); } void main() { } struct CheckedInt(N) if (isIntegral!N) { private N value; this(N value) { this.value = value; } property auto Value() { return value; } // addition CheckedInt opBinary(string op)(CheckedInt rhs) if (op == "+") { auto result = value + rhs.value; enforce(rhs.value >= 0 ? result >= value : result < value); return result; } } I don't understand why he's trying to return result here (unless it was a mistake). Result is going to have the type of "private N value", whatever N may be, and this is conflicting with the return type which is CheckedInt. So, this won't compile. He has the same returns for subtraction and multiplication, but for others like division, shift and bitwise overload he has this: // division and remainder CheckedInt opBinary(string op)(CheckedInt rhs) if (op == "/" || op == "%") { enforce(rhs.value != 0); return CheckedInt(mixin("value" ~ op ~ "rhs.value")); } This looks correct. If I change the add overload from the code to this: // addition CheckedInt opBinary(string op)(CheckedInt rhs) if (op == "+") { auto result = value + rhs.value; enforce(rhs.value >= 0 ? result >= value : result < value); return CheckedInt(mixin("value" ~ op ~ "rhs.value")); } Then the return statement calls a CheckedInt constructor and I get back a CheckedInt struct with the right value in the private variable "value". What I don't understand is how the constructor can be called like that. In my example the mixin would convert the code to: return CheckedInt(10); But if I ever tried a call like "CheckedInt(10)" in a unittest block, it wouldn't work. So how does this magic work?
Aug 24 2010
On Tue, 24 Aug 2010 17:19:25 -0500, Andrej Mitrovic <andrej.mitrovich whatever.com> wrote:[snip] struct CheckedInt(N) if (isIntegral!N) { private N value; this(N value) { this.value = value; } property auto Value() { return value; } // addition CheckedInt opBinary(string op)(CheckedInt rhs) if (op == "+") { auto result = value + rhs.value; enforce(rhs.value >= 0 ? result >= value : result < value); return result; } } I don't understand why he's trying to return result here (unless it was a mistake). Result is going to have the type of "private N value", whatever N may be, and this is conflicting with the return type which is CheckedInt. So, this won't compile.That's a bug. The return value should be CheckedInt(result);He has the same returns for subtraction and multiplication, but for others like division, shift and bitwise overload he has this: // division and remainder CheckedInt opBinary(string op)(CheckedInt rhs) if (op == "/" || op == "%") { enforce(rhs.value != 0); return CheckedInt(mixin("value" ~ op ~ "rhs.value")); } This looks correct. If I change the add overload from the code to this: // addition CheckedInt opBinary(string op)(CheckedInt rhs) if (op == "+") { auto result = value + rhs.value; enforce(rhs.value >= 0 ? result >= value : result < value); return CheckedInt(mixin("value" ~ op ~ "rhs.value")); } Then the return statement calls a CheckedInt constructor and I get back a CheckedInt struct with the right value in the private variable "value". What I don't understand is how the constructor can be called like that. In my example the mixin would convert the code to: return CheckedInt(10); But if I ever tried a call like "CheckedInt(10)" in a unittest block, it wouldn't work. So how does this magic work?http://www.digitalmars.com/d/2.0/mixin.html -- Yao G.
Aug 24 2010
Yao G. Wrote:That's a bug. The return value should be CheckedInt(result);I'll add that to the errata. Yao G. Wrote:http://www.digitalmars.com/d/2.0/mixin.htmlI wasn't refering to the mixin, but the call to CheckedInt(). mixin compiles "value" ~ op ~ "rhs.value", which in this case evaluates to 5 + 5 and the whole call becomes CheckedInt(10). What I don't understand is how you can construct a new CheckedInt struct by calling it with CheckedInt(10), when I have to use a call like CheckedInt!(int)(10) outside the struct (in main or in a unittest block).
Aug 24 2010
On Tue, 24 Aug 2010 17:43:49 -0500, Andrej Mitrovic <andrej.mitrovich whatever.com> wrote:I wasn't refering to the mixin, but the call to CheckedInt(). mixin compiles "value" ~ op ~ "rhs.value", which in this case evaluates to 5 + 5 and the whole call becomes CheckedInt(10).Sorry. My mistake, I probably misread.What I don't understand is how you can construct a new CheckedInt struct by calling it with CheckedInt(10), when I have to use a call like CheckedInt!(int)(10) outside the struct (in main or in a unittest block).This, I don't know. Maybe when you are inside the struct's body the compiler somehow can infer the type when CheckedInt is instantiated. Or maybe it does some kind of automatic alias (alias CheckedInt!10 CheckedInt). Outside of the struct's body, as there aren't template default values, is not possible to infer the type of CheckedInt. Maybe someone with more experience on templates can give you a better (or correct, I'm just guessing this :) ) answer. -- Yao G.
Aug 24 2010
Andrej Mitrovic wrote:Yao G. Wrote:It is the same in C++: the name of the template is equivalent to the current instantiation of the template. foo() and bar() are both legal: template <class T> class C { public: C foo() { return C(); } C<T> bar() { return C<T>(); } }; int main() { C<int> c; c.foo(); c.bar(); } It seems to be the same in D. I don't know whether this is intended, or just a left over from the C++ parts of dmd. (I assume dmd shares code with the Digital Mars C++ compiler.) AliThat's a bug. The return value should be CheckedInt(result);I'll add that to the errata. Yao G. Wrote:http://www.digitalmars.com/d/2.0/mixin.htmlI wasn't refering to the mixin, but the call to CheckedInt(). mixin compiles "value" ~ op ~ "rhs.value", which in this case evaluates to 5 + 5 and the whole call becomes CheckedInt(10). What I don't understand is how you can construct a new CheckedInt struct by calling it with CheckedInt(10), when I have to use a call like CheckedInt!(int)(10) outside the struct (in main or in a unittest block).
Aug 24 2010
Ali Çehreli:It is the same in C++: the name of the template is equivalent to the current instantiation of the template. ... It seems to be the same in D. I don't know whether this is intended, or just a left over from the C++ parts of dmd. (I assume dmd shares code with the Digital Mars C++ compiler.)I don't like this "feature" much, it has caused me troubles: http://d.puremagic.com/issues/show_bug.cgi?id=3950 Bye, bearophile
Aug 24 2010
On Tue, 24 Aug 2010 18:43:49 -0400, Andrej Mitrovic <andrej.mitrovich whatever.com> wrote:I wasn't refering to the mixin, but the call to CheckedInt(). mixin compiles "value" ~ op ~ "rhs.value", which in this case evaluates to 5 + 5 and the whole call becomes CheckedInt(10). What I don't understand is how you can construct a new CheckedInt struct by calling it with CheckedInt(10), when I have to use a call like CheckedInt!(int)(10) outside the struct (in main or in a unittest block).Inside a template instantiation, the template name without template parameters is equivalent to the current instantiation. It saves a lot of typing. -Steve
Aug 25 2010
Steven Schveighoffer:Inside a template instantiation, the template name without template parameters is equivalent to the current instantiation. It saves a lot of typing.And causes some troubles :-) It's a barter, and I am not sure it's a good one. Bye, bearophile
Aug 25 2010
On Wed, 25 Aug 2010 09:55:47 -0400, bearophile <bearophileHUGS lycos.com> wrote:Steven Schveighoffer:What trouble? I've already stated in the bug report how there's not anything better you can expect from the compiler. If it was required to specify the template parameters instead of using the shortcut, then you would be no better off. Here is something I had found to be a problem with dcollections: final class TreeMap(K, V, alias ImplTemp=RBTree, alias compareFunc=DefaultCompare) { ... TreeMap!(K, V) set(K k, V v) {...} ... } Notice the problem? -SteveInside a template instantiation, the template name without template parameters is equivalent to the current instantiation. It saves a lot of typing.And causes some troubles :-) It's a barter, and I am not sure it's a good one.
Aug 25 2010
Ok I think I am kind of getting this. The template name inside a template is it's instantiation. I can do "CheckedInt.variable" and get back the value of "variable" in the current instantiation. The trouble is, when you do a call like CheckedInt() you will loose all other data that you had before: module binary_ops; import std.stdio : writeln; import std.traits; import std.exception; unittest { auto foo = CheckedInt!(int)(5); auto bar = CheckedInt!(int)(5); foo.x = 4; bar.x = 5; foo = foo + bar; writeln(foo.x); // writes 0 writeln(bar.x); // writes 5 } void main() { } struct CheckedInt(N) if (isIntegral!N) { private N value; int x; this(N value) { this.value = value; } // addition CheckedInt opBinary(string op)(CheckedInt rhs) if (op == "+") { auto result = value + rhs.value; enforce(rhs.value >= 0 ? result >= value : result < value); return CheckedInt(result); } } Here I've lost the value of x. "return CheckedInt(result);" calls the constructor of the already instantiated template, but because of the way D works (afaik) it first has to deconstruct the object before constructing it again. And that includes initializing all members to their .init value before calling the constructor. So, I don't like that return statement at all.. Steven Schveighoffer Wrote:On Tue, 24 Aug 2010 18:43:49 -0400, Andrej Mitrovic <andrej.mitrovich whatever.com> wrote:I wasn't refering to the mixin, but the call to CheckedInt(). mixin compiles "value" ~ op ~ "rhs.value", which in this case evaluates to 5 + 5 and the whole call becomes CheckedInt(10). What I don't understand is how you can construct a new CheckedInt struct by calling it with CheckedInt(10), when I have to use a call like CheckedInt!(int)(10) outside the struct (in main or in a unittest block).Inside a template instantiation, the template name without template parameters is equivalent to the current instantiation. It saves a lot of typing. -Steve
Aug 25 2010
On Wed, 25 Aug 2010 10:01:31 -0400, Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:Ok I think I am kind of getting this. The template name inside a template is it's instantiation. I can do "CheckedInt.variable" and get back the value of "variable" in the current instantiation. The trouble is, when you do a call like CheckedInt() you will loose all other data that you had before: module binary_ops; import std.stdio : writeln; import std.traits; import std.exception; unittest { auto foo = CheckedInt!(int)(5); auto bar = CheckedInt!(int)(5); foo.x = 4; bar.x = 5; foo = foo + bar; writeln(foo.x); // writes 0 writeln(bar.x); // writes 5 } void main() { } struct CheckedInt(N) if (isIntegral!N) { private N value; int x; this(N value) { this.value = value; } // addition CheckedInt opBinary(string op)(CheckedInt rhs) if (op == "+") { auto result = value + rhs.value; enforce(rhs.value >= 0 ? result >= value : result < value); return CheckedInt(result); } } Here I've lost the value of x. "return CheckedInt(result);" calls the constructor of the already instantiated template, but because of the way D works (afaik) it first has to deconstruct the object before constructing it again. And that includes initializing all members to their .init value before calling the constructor.A struct is a value type, so you are making a copy regardless. Your expectation that foo = ... does not wholly replace foo is incorrect. You could do this: this(N value, int x) { this.value = value; this.x = x; } ... return CheckedInt(result, x); ... and then you're x comes through. Value types are always passed by value unless you use pointers or ref. BTW, this has nothing to do with CheckedInt being a shortcut for CheckedInt!N inside the instantiated template. -Steve
Aug 25 2010
That wasn't my expectation, but nevermind. The example is ok in this case, it's just that care needs to be taken when making operator overloads. So maybe I overreacted a little. :) Steven Schveighoffer Wrote:A struct is a value type, so you are making a copy regardless. Your expectation that foo = ... does not wholly replace foo is incorrect.
Aug 25 2010
On Tue, 24 Aug 2010 18:19:25 -0400, Andrej Mitrovic <andrej.mitrovich whatever.com> wrote:What I don't understand is how the constructor can be called like that. In my example the mixin would convert the code to: return CheckedInt(10); But if I ever tried a call like "CheckedInt(10)" in a unittest block, it wouldn't work. So how does this magic work?BTW, a unit test model I use in dcollections quite a bit is putting unit tests inside the template itself. Then the unit test enjoys the same benefits. e.g.: struct CheckedInt(N) if(isIntegral!N) { void foo(N n) { ... } unittest { CheckedInt ci; ci.foo(1); } } The only caveat is you have to instantiate the template to get the unit test to run :) So on the bottom of your module you have to do this: unittest { CheckedInt!int ci1; CheckedInt!uint ci2; ... } The great benefit however is if you write your unit tests in a generic way, you can rigorously test your struct with all possible template instantiations without writing any extra code. I've found the only limit here is generating data -- the literals have to match the N type. CheckedInt only instantiates if N is integral, but if that constraint wasn't there, then CheckedInt!string wouldn't compile because the unit test can't convert the literal 1 to a string. -Steve
Aug 25 2010
Interesting. :) The following seems to work, although I don't know if that's a good idea?: struct CheckedInt(N) // if(isIntegral!N) { void foo(N n) { } unittest { CheckedInt ci; ci.foo(N.init); } } unittest { CheckedInt!int ci1; CheckedInt!uint ci2; CheckedInt!string ci3; } void main() { } Steven Schveighoffer Wrote:I've found the only limit here is generating data -- the literals have to match the N type. CheckedInt only instantiates if N is integral, but if that constraint wasn't there, then CheckedInt!string wouldn't compile because the unit test can't convert the literal 1 to a string. -Steve
Aug 25 2010
Although that might not work with floats since that would call foo with a NaN value, and if it used that, well that wouldn't really work. I could use N.max, but that doesn't work with strings. Andrej Mitrovic Wrote:The following seems to work, although I don't know if that's a good idea?:
Aug 25 2010
What would be really cool is if we had a property that returned a random value of any integrated type. And for user-defined types, maybe it would call a method with a special name. I guess one could make a template function that would do just that. Andrej Mitrovic Wrote:Although that might not work with floats since that would call foo with a NaN value, and if it used that, well that wouldn't really work. I could use N.max, but that doesn't work with strings. Andrej Mitrovic Wrote:The following seems to work, although I don't know if that's a good idea?:
Aug 25 2010
On Wed, Aug 25, 2010 at 17:27, Andrej Mitrovic <andrej.mitrovich test.com>wrote:What would be really cool is if we had a property that returned a random value of any integrated type. And for user-defined types, maybe it would call a method with a special name. I guess one could make a template function that would do just that.It reminds me a bit of Haskell's QuickCheck library, used to test code. http://hackage.haskell.org/package/QuickCheck-2.1.1.1 There is an Arbitrary typeclass that the user defines to generate an arbitrary value for a given type. With a D template, it's easy to do that for all numeric types (or, any 'range' type, like char) and arrays / associative arrays. This way, generating it for classes and struct is easy. The real difficulty would be to generate an arbitrary function from int function(int), for example. And, to test, you don't want a perfectly random value: you need the extrema, degenerate cases like NaN, null pointers, empty arrays, etc. Philippe
Aug 26 2010