Thread overview
Semantics of postfix ops for classes
Jul 20, 2012
Andrej Mitrovic
Jul 25, 2012
Don Clugston
Jul 25, 2012
Christophe Travert
July 20, 2012
According to TDPL postfix operators are rewritten to call prefix operators, e.g. on this call for some user-type object named a:

auto b = a++;

// is converted to:
auto b = ((ref x) { auto t = x; ++x; return t; })(a);

But I don't see how this is reasonable for classes. Examine:

struct Struct {
    int x = 1;
    Struct opUnary(string op : "++")() {
        x++;
        return this;
    }
}

class Class {
    int x = 1;
    Class opUnary(string op : "++")() {
        x++;
        return this;
    }
}

void main()
{
    Struct foo1;
    Struct foo2 = foo1++;
    assert(foo1.x != foo2.x);  // ok

    Class bar1 = new Class;
    Class bar2 = bar1++;
    assert(bar1.x != bar2.x);  // fail
}

It's clear why, the rewrite that calls "auto t = x" simply binds another reference to the same object.

Unfortunately this makes it hard to wrap C++ libraries which have both prefix/postfix operators defined. Currently I wrap these in e.g. "preInc"/"postInc" methods and I explicitly disable the prefix/postfix opUnary methods.

Are the semantics of this rewrite ok with people who use op overloads? I found them to be surprising, but then again I don't use op overloads that much, I'm just noticing the difference between C++ and D.
July 25, 2012
On 20/07/12 17:12, Andrej Mitrovic wrote:
> According to TDPL postfix operators are rewritten to call prefix
> operators, e.g. on this call for some user-type object named a:
>
> auto b = a++;
>
> // is converted to:
> auto b = ((ref x) { auto t = x; ++x; return t; })(a);
>
> But I don't see how this is reasonable for classes. Examine:
>
> struct Struct {
>      int x = 1;
>      Struct opUnary(string op : "++")() {
>          x++;
>          return this;
>      }
> }
>
> class Class {
>      int x = 1;
>      Class opUnary(string op : "++")() {
>          x++;
>          return this;
>      }
> }
>
> void main()
> {
>      Struct foo1;
>      Struct foo2 = foo1++;
>      assert(foo1.x != foo2.x);  // ok
>
>      Class bar1 = new Class;
>      Class bar2 = bar1++;
>      assert(bar1.x != bar2.x);  // fail
> }
>
> It's clear why, the rewrite that calls "auto t = x" simply binds
> another reference to the same object.
>
> Unfortunately this makes it hard to wrap C++ libraries which have both
> prefix/postfix operators defined. Currently I wrap these in e.g.
> "preInc"/"postInc" methods and I explicitly disable the prefix/postfix
> opUnary methods.
>
> Are the semantics of this rewrite ok with people who use op overloads?
> I found them to be surprising, but then again I don't use op overloads
> that much, I'm just noticing the difference between C++ and D.

But classes have reference semantics, so they are already completely different from C++.

The question really is, do postfix ++ and -- make sense for reference types? Arguably not. From a theoretical sense, the existing behaviour does make sense, but in practice, every time it is used, it is probably a bug.

The only other reasonable option I can think of would be to make class++ be of type void, so that you could still write
bar1++;
but not bar2 = bar1++;
since the existing behaviour can be achieved by writing bar2 = ++ bar1;



July 25, 2012
Don Clugston , dans le message (digitalmars.D:173192), a écrit :
> The question really is, do postfix ++ and -- make sense for reference types? Arguably not. From a theoretical sense, the existing behaviour does make sense, but in practice, every time it is used, it is probably a bug.
> 
> The only other reasonable option I can think of would be to make class++
> be of type void, so that you could still write
> bar1++;
> but not bar2 = bar1++;
> since the existing behaviour can be achieved by writing bar2 = ++ bar1;

Similarly, the langage should provide a way to disable postfix++ on a struct, since a struct can be a reference type.