www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - TDPL: Operator Overloading

reply Andrej Mitrovic <andrej.mitrovich whatever.com> writes:
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
next sibling parent reply "Yao G." <yao.gomez spam.gmail.com> writes:
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
parent reply Andrej Mitrovic <andrej.mitrovich whatever.com> writes:
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.html
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).
Aug 24 2010
next sibling parent "Yao G." <yao.gomez spam.gmail.com> writes:
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
prev sibling next sibling parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
Andrej Mitrovic wrote:
 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.html
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).
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.) Ali
Aug 24 2010
parent bearophile <bearophileHUGS lycos.com> writes:
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
prev sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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
next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
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
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 25 Aug 2010 09:55:47 -0400, bearophile <bearophileHUGS lycos.com>  
wrote:

 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.
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? -Steve
Aug 25 2010
prev sibling parent reply Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
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
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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
parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
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
prev sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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
parent reply Andrej Mitrovic <andrej.mitrovich test.com> writes:
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
parent reply Andrej Mitrovic <andrej.mitrovich test.com> writes:
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
parent reply Andrej Mitrovic <andrej.mitrovich test.com> writes:
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
parent Philippe Sigaud <philippe.sigaud gmail.com> writes:
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