www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - const references in C++ and D

reply bearophile <bearophileHUGS lycos.com> writes:
I am translating a small C++ program to D2 and I am having problems. I am
learning, but my mind is not large enough for all the subtleties of both C++
and D2 yet :-)

This C++ program compiles:


struct Vec {
    void operator+(const Vec& other) {}
};
Vec bar(Vec x) {
    return x;
}
int main() {
    Vec() + bar(Vec());
}



I think this is the equivalent D2 program:

// program #1
struct Vec {
    void opBinary(string Op:"+")(const ref Vec other) {}
}
Vec bar(Vec x) {
    return x;
}
void main() {
    Vec() + bar(Vec()); // line 9
}


But DMD gives me:
temp3.d(9): Error: function temp3.Vec.opBinary!("+").opBinary (ref const
const(Vec) other) is not callable using argument types (Vec)
temp3.d(9): Error: bar((Vec())) is not an lvalue


I vaguely remember a discussion about this in the D newsgroup, that D acts like
this on purpose, so I think this is not a D bug. What is the right way to
translate that C++ code to D2? (So far I have just commented out the 'ref' in
opBinary).


Another case, to me it seems the same problem (D2 code):


// program #2
struct Vec {
    Vec opOpAssign(string Op:"+=")(ref Vec other) {
        return this;
    }
    Vec opBinary(string Op:"*")(int k) {
        return this;
    }
}
void main() {
    Vec x;
    x += Vec() * 2; // line 12
}


DMD prints:
temp3.d(12): Error: function temp3.Vec.opOpAssign!("+=").opOpAssign (ref Vec
other) is not callable using argument types (Vec)
temp3.d(12): Error: (Vec()).opBinary(2) is not an lvalue

Again I have just commented out the 'ref' here and in most other operator
overloading methods.


I have seen that in some cases I can use "auto ref" (like in those two
examples), but not in all of them, I don't know why. For example here:


// program #3
struct Vec {
    Vec opOpAssign(string Op)(auto ref Vec other) if (Op == "+=") {
        return this;
    }
    Vec opBinary(string Op:"+")(Vec other) {
        Vec result;
        return result += other;
    }
}
void main() {
    Vec v;
    v += Vec() + Vec(); // line 13
}


DMD prints:
temp3.d(13): Error: function temp3.Vec.opOpAssign!("+=").opOpAssign (auto ref
Vec other) is not callable using argument types (Vec)
temp3.d(13): Error: (Vec()).opBinary((Vec())) is not an lvalue

Do you know why "auto ref" isn't right here?

Bye and thank you,
bearophile
May 30 2010
next sibling parent reply BCS <none anon.com> writes:
Hello bearophile,

 I am translating a small C++ program to D2 and I am having problems. I
 am learning, but my mind is not large enough for all the subtleties of
 both C++ and D2 yet :-)
 
 This C++ program compiles:
 
 struct Vec {
 void operator+(const Vec& other) {}
 };
 Vec bar(Vec x) {
 return x;
 }
 int main() {
 Vec() + bar(Vec());
 }
 I think this is the equivalent D2 program:
 
 // program #1
 struct Vec {
 void opBinary(string Op:"+")(const ref Vec other) {}
 }
 Vec bar(Vec x) {
 return x;
 }
 void main() {
 Vec() + bar(Vec()); // line 9
 }
 But DMD gives me:
 
 temp3.d(9): Error: function temp3.Vec.opBinary!("+").opBinary (ref
 const const(Vec) other) is not callable using argument types (Vec)
 
 temp3.d(9): Error: bar((Vec())) is not an lvalue
 
 I vaguely remember a discussion about this in the D newsgroup, that D
 acts like this on purpose, so I think this is not a D bug.

Yes, by design, you can't have a reference to a temporary value.
 What is the
 right way to translate that C++ code to D2? (So far I have just
 commented out the 'ref' in opBinary).

Thats' what I'd do.
 
 Another case, to me it seems the same problem (D2 code):
 
 // program #2
 struct Vec {
 Vec opOpAssign(string Op:"+=")(ref Vec other) {
 return this;
 }
 Vec opBinary(string Op:"*")(int k) {
 return this;
 }
 }
 void main() {
 Vec x;
 x += Vec() * 2; // line 12
 }
 DMD prints:
 
 temp3.d(12): Error: function temp3.Vec.opOpAssign!("+=").opOpAssign
 (ref Vec other) is not callable using argument types (Vec)
 
 temp3.d(12): Error: (Vec()).opBinary(2) is not an lvalue
 
 Again I have just commented out the 'ref' here and in most other
 operator overloading methods.
 
 I have seen that in some cases I can use "auto ref" (like in those two
 examples), but not in all of them, I don't know why. For example here:
 
 // program #3
 struct Vec {
 Vec opOpAssign(string Op)(auto ref Vec other) if (Op == "+=") {
 return this;
 }
 Vec opBinary(string Op:"+")(Vec other) {
 Vec result;
 return result += other;
 }
 }
 void main() {
 Vec v;
 v += Vec() + Vec(); // line 13
 }
 DMD prints:
 
 temp3.d(13): Error: function temp3.Vec.opOpAssign!("+=").opOpAssign
 (auto ref Vec other) is not callable using argument types (Vec)
 
 temp3.d(13): Error: (Vec()).opBinary((Vec())) is not an lvalue
 
 Do you know why "auto ref" isn't right here?
 
 Bye and thank you,
 bearophile

... <IXOYE><
May 30 2010
parent reply bearophile <bearophileHUGS lycos.com> writes:
BCS:
 Thats' what I'd do.

But wasn't "auto ref" invented for such situations? Bye and thank you, bearophile
May 30 2010
next sibling parent reply BCS <none anon.com> writes:
Hello bearophile,

 BCS:
 
 Thats' what I'd do.
 


That would work too, assuming that the internals of the function allow it.
 Bye and thank you,
 bearophile

... <IXOYE><
May 30 2010
parent reply bearophile <bearophileHUGS lycos.com> writes:
BCS:
 That would work too, assuming that the internals of the function allow it.

I don't know what you mean (in that example 3 auto ref doesn't compile, I don't know if it's a compiler bug or if I am doing something wrong). Bye and thank you, bearophile
May 31 2010
parent bearophile <bearophileHUGS lycos.com> writes:
Trass3r:
 In which cases does it work/doesn't it work?

This doesn't work, I have shown the given error messages in the original post: //#3 struct Vec { Vec opOpAssign(string Op)(auto ref Vec other) if (Op == "+=") { return this; } Vec opBinary(string Op:"+")(Vec other) { Vec result; return result += other; } } void main() { Vec v; v += Vec() + Vec(); // line 13 } It works if you remove "auto ref". I think this is a compiler bug. I will put it in bugzilla. The program #1 and 2# are instead correct according to D specs. Bye, bearophile
May 31 2010
prev sibling parent Trass3r <un known.com> writes:
 I don't know what you mean (in that example 3 auto ref doesn't compile,  
 I don't know if it's a compiler bug or if I am doing something wrong).

Looks like a compiler bug to me. In which cases does it work/doesn't it work?
May 31 2010
prev sibling next sibling parent reply Kagamin <spam here.lot> writes:
I think, it's a bug. It was denied to have write access to the temporary
variable, but you request only read access which is safe to be provided.
May 31 2010
next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Kagamin:

 I think, it's a bug. It was denied to have write access to the temporary
variable, but you request only read access which is safe to be provided.<

Do you mean the program #3? Thank you for your answers. For me sometimes it's not easy to tell apart my bugs from the compiler bugs. Bye, bearophile
May 31 2010
parent Kagamin <spam here.lot> writes:
bearophile Wrote:

 Kagamin:
 
 I think, it's a bug. It was denied to have write access to the temporary
variable, but you request only read access which is safe to be provided.<

Do you mean the program #3?

May 31 2010
prev sibling parent Trass3r <un known.com> writes:
 I think, it's a bug. It was denied to have write access to the temporary  
 variable, but you request only read access which is safe to be provided.

True, there should be an additional check if it's a const ref.
May 31 2010
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Sun, 30 May 2010 19:03:57 -0400, bearophile <bearophileHUGS lycos.com>  
wrote:

 I am translating a small C++ program to D2 and I am having problems. I  
 am learning, but my mind is not large enough for all the subtleties of  
 both C++ and D2 yet :-)

 This C++ program compiles:


 struct Vec {
     void operator+(const Vec& other) {}
 };
 Vec bar(Vec x) {
     return x;
 }
 int main() {
     Vec() + bar(Vec());
 }



 I think this is the equivalent D2 program:

 // program #1
 struct Vec {
     void opBinary(string Op:"+")(const ref Vec other) {}
 }
 Vec bar(Vec x) {
     return x;
 }
 void main() {
     Vec() + bar(Vec()); // line 9
 }


 But DMD gives me:
 temp3.d(9): Error: function temp3.Vec.opBinary!("+").opBinary (ref const  
 const(Vec) other) is not callable using argument types (Vec)
 temp3.d(9): Error: bar((Vec())) is not an lvalue


 I vaguely remember a discussion about this in the D newsgroup, that D  
 acts like this on purpose, so I think this is not a D bug. What is the  
 right way to translate that C++ code to D2? (So far I have just  
 commented out the 'ref' in opBinary).


 Another case, to me it seems the same problem (D2 code):


 // program #2
 struct Vec {
     Vec opOpAssign(string Op:"+=")(ref Vec other) {
         return this;
     }
     Vec opBinary(string Op:"*")(int k) {
         return this;
     }
 }
 void main() {
     Vec x;
     x += Vec() * 2; // line 12
 }


 DMD prints:
 temp3.d(12): Error: function temp3.Vec.opOpAssign!("+=").opOpAssign (ref  
 Vec other) is not callable using argument types (Vec)
 temp3.d(12): Error: (Vec()).opBinary(2) is not an lvalue

 Again I have just commented out the 'ref' here and in most other  
 operator overloading methods.


 I have seen that in some cases I can use "auto ref" (like in those two  
 examples), but not in all of them, I don't know why. For example here:


 // program #3
 struct Vec {
     Vec opOpAssign(string Op)(auto ref Vec other) if (Op == "+=") {
         return this;
     }
     Vec opBinary(string Op:"+")(Vec other) {
         Vec result;
         return result += other;
     }
 }
 void main() {
     Vec v;
     v += Vec() + Vec(); // line 13
 }


 DMD prints:
 temp3.d(13): Error: function temp3.Vec.opOpAssign!("+=").opOpAssign  
 (auto ref Vec other) is not callable using argument types (Vec)
 temp3.d(13): Error: (Vec()).opBinary((Vec())) is not an lvalue

 Do you know why "auto ref" isn't right here?

I've had a long private discussion with Andrei about this problem. C++ allows rvalues to be passed into functions that accept const ref. However, it surprisingly is less optimal for rvalues to pass by ref. The reason is because, you must put the temporary on the stack, and then also put the reference on the stack. By simply passing the struct without ref, it's not copied as an lvalue would be, it's simply used where it is, because the compiler knows that it's no longer needed in the calling function. So the optimizer can work more efficiently to remove those extra pushes and pops. In C++, the optimial solution would be to allow the following duplicated methods: foo(const ref Vec); foo(const Vec); as overloads, with the compiler choosing the first for lvalues and the second for rvalues. But you cannot overload based on ref, so this is not allowed. D's solution is to use auto ref, but I think your attempts to use it show that it doesn't work. You should file a bug with your program 3. -Steve
Jun 01 2010
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
Steven Schveighoffer:
 D's solution is to use auto ref, but I think your attempts to use it show  
 that it doesn't work.  You should file a bug with your program 3.

Thank you for your always useful explanations, Steven (this is a fragile bug report, if you change the code a bit the bug vanishes). http://d.puremagic.com/issues/show_bug.cgi?id=4258 Bye, bearophile
Jun 01 2010
prev sibling parent reply Kagamin <spam here.lot> writes:
Steven Schveighoffer Wrote:

 However, it surprisingly is less optimal for rvalues to pass by ref.

So what? It just must work. Efficiency of a particular operation doesn't mean efficiency of a program: it can use lvalues most of time.
 D's solution is to use auto ref, but I think your attempts to use it show  
 that it doesn't work.  You should file a bug with your program 3.

auto ref works only for return values, implementing it for parameters means combinatorial bloat.
Jun 03 2010
next sibling parent Kagamin <spam here.lot> writes:
Steven Schveighoffer Wrote:

 But Andrei feels very strongly that passing temporaries via const ref is a  
 complete mess in C++, so you'll have to convince him.

If the issue is not performance, it shouldn't be mentioned from the start.
Jun 03 2010
prev sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Kagamin:
 auto ref works only for return values,

In most cases seems to work with (input) arguments too. Bye, bearophile
Jun 03 2010
parent Kagamin <spam here.lot> writes:
bearophile Wrote:

 Kagamin:
 auto ref works only for return values,

In most cases seems to work with (input) arguments too.

Jun 03 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 03 Jun 2010 09:09:39 -0400, Kagamin <spam here.lot> wrote:

 Steven Schveighoffer Wrote:

 However, it surprisingly is less optimal for rvalues to pass by ref.

So what? It just must work. Efficiency of a particular operation doesn't mean efficiency of a program: it can use lvalues most of time.

Using lvalues is just as inefficient as passing temporaries via ref. The issue is that you can get *better* performance by passing temporaries by value than you can get by passing lvalues by value or by reference. Without something like auto ref, you put achieving the highest performance at odds with usability.
 D's solution is to use auto ref, but I think your attempts to use it  
 show
 that it doesn't work.  You should file a bug with your program 3.

auto ref works only for return values, implementing it for parameters means combinatorial bloat.

At this point, the problem is that there is *nothing* that works for both rvalues and lvalues. auto ref would be something that works, even if it meant duplicating generated code. I personally don't see a huge problem with allowing temporaries to be passed via ref const. It could be a tradeoff between low performance vs. smaller footprint. But Andrei feels very strongly that passing temporaries via const ref is a complete mess in C++, so you'll have to convince him. -Steve
Jun 03 2010
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 03 Jun 2010 09:47:19 -0400, Kagamin <spam here.lot> wrote:

 Steven Schveighoffer Wrote:

 But Andrei feels very strongly that passing temporaries via const ref  
 is a
 complete mess in C++, so you'll have to convince him.

If the issue is not performance, it shouldn't be mentioned from the start.

*My* issue is not performance. Andrei's is. I just want something that works. Andrei doesn't want to add some feature that he thinks is a failure in C++. I'm speaking too much for Andrei here, but I think this is the way he feels. -Steve
Jun 03 2010