www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Does D really need something like const&?

reply "Namespace" <rswhite4 googlemail.com> writes:
Currently small structs are moved as rvalues and copied as 
lvalues and that seems like the most performant way.
But for massy structs this doesn't make sense, if you must pass 
them to functions as parameter, without declaring the parameter 
as 'ref'.
Why? Because in general this is very unperformant and that is 
mostly not what you want.
So I thougth like many other still do: something is missing here, 
we need something like const&.
But: why do we need something like that?
The more important question is: why is our massy struct a struct 
and not a class?
Does it make sense that it isn't a class? If we could answer this 
question generally with No or if we don't have any important 
reason why the massy struct should stay a struct and not a class, 
then we do not need something like const&.
If your data is massy: use a class. If not and you don't need 
polymorphism: use a struct. Done! ... or not?
We should ask us:
1. How could we answer the questions above?
2. Do we really need something that takes both, rvalues and 
lvalues, if it isn't a template?
I'm really interested to hear what is your opinion.
Mar 01 2013
next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 01 Mar 2013 16:16:46 -0500, Namespace <rswhite4 googlemail.com>  
wrote:

 If your data is massy: use a class. If not and you don't need  
 polymorphism: use a struct. Done! ... or not?

Size is not a major factor when I decide whether to use a struct or class. I don't think the decision for class or struct should be inextricably linked to data size. And I believe, actually, that passing a massive struct by value if it's an rvalue IS the most performant -- no copy needed, no referencing needed. However, I agree we need SOMETHING to avoid writing every function at least twice. I'd also point out that D's notion of rvalue vs. lvalue is too limited, and results in unnecessary restrictions. We may need a marker to tell the compiler whether a type is an rvalue or not. I'd also like to have a way to specify a struct member function takes it's 'this' parameter by value. -Steve
Mar 01 2013
prev sibling next sibling parent "Namespace" <rswhite4 googlemail.com> writes:
Size is next polymorphism my main reason why I would use a class 
instead of a struct.
A good heuristic size (which I have heard here) was: <= 16 bytes 
-> struct, > 16 bytes -> class.
And what are your reasons for decision?

 And I believe, actually, that passing a massive struct by value 
 if it's an  rvalue IS the most performant -- no copy needed, no 
 referencing needed.

And what a massive struct do you think? For example, if you have something like this: struct Massy { public: int[1024] marr; } I would bet that it is usually better to take 'Massy' by ref, or to use a class, instead of a move or a copy.
Mar 01 2013
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 01 Mar 2013 17:20:00 -0500, Namespace <rswhite4 googlemail.com>  
wrote:

 Size is next polymorphism my main reason why I would use a class instead  
 of a struct.
 A good heuristic size (which I have heard here) was: <= 16 bytes ->  
 struct, > 16 bytes -> class.
 And what are your reasons for decision?

Features of classes vs. structs. Need an interface? class. Need fine control over lifetime? struct. There are lots of other features that can make the decision. Size is a very small part of it.
 And I believe, actually, that passing a massive struct by value if it's  
 an  rvalue IS the most performant -- no copy needed, no referencing  
 needed.

And what a massive struct do you think? For example, if you have something like this: struct Massy { public: int[1024] marr; } I would bet that it is usually better to take 'Massy' by ref, or to use a class, instead of a move or a copy.

foo(Massy m) { for(int i = 0; i < sizeof(m.marr); ++i) m.marr[i] = i; } main() { foo(Massy()); } OK, so let's take the case that foo accepts Massy by ref: 1. Massy has to be pushed onto the stack 2. A ref to Massy is pushed onto the stack 3. Every access to m in foo must go through a pointer dereference (there are 1024 of them) What if Massy is accepted by value? 1. Massy has to be pushed onto the stack 2. Massy is NOT copied to pass to foo, it's passed directly, there is no copy involved! No ref is needed either 3. Every access to m does NOT need to be dereferenced. I agree, lvalues, Massy needs to be passed by ref. But the case can be made that passing rvalues by ref is lower performance, even if slightly. Often times though, I would think that it's not worth the trouble to have both a ref and non-ref foo. In that case, passing foo by ref is preferred, because the penalty is only slight in passing an rvalue by reference, but is humongous when passing an lvalue by value. auto ref was supposed to be this magic feature. -Steve
Mar 01 2013
prev sibling next sibling parent "Namespace" <rswhite4 googlemail.com> writes:
I know about 'auto ref'. Nice try but ... ;)

 Need fine control over lifetime?

on the stack so you have also controll over the instance lifetime. -> No reason for struct. Interfaces, well, but I'm sure you can live without them sometimes.
Mar 01 2013
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 01 Mar 2013 18:05:12 -0500, Namespace <rswhite4 googlemail.com>  
wrote:

 I know about 'auto ref'. Nice try but ... ;)

Nice try? I don't get this. It was supposed to be the analogue to C++ rvalue references, Walter did not implement it as Andrei expected (AIUI). He made it a (admittedly useful) template feature.
 Need fine control over lifetime?

stack so you have also controll over the instance lifetime. -> No reason for struct.

scoped is implemented via a struct...
 Interfaces, well, but I'm sure you can live without them sometimes.

OK, so by eliminating my use cases that are problematic for your theory, yes, I guess I can base all my decisions on size ;) -Steve
Mar 01 2013
prev sibling next sibling parent "Namespace" <rswhite4 googlemail.com> writes:
 Nice try?  I don't get this.  It was supposed to be the 
 analogue to C++ rvalue references, Walter did not implement it 
 as Andrei expected (AIUI).  He made it a (admittedly useful) 
 template feature.

I got the answer that 'auto ref' is not the solution which will solve the const& issue and because of that that, it will probably never be implemented for non-template functions. So first of all they must find a new suitable solution for that.
 scoped is implemented via a struct...

stack and controll the instance lifetime also.
 Interfaces, well, but I'm sure you can live without them 
 sometimes.

OK, so by eliminating my use cases that are problematic for your theory, yes, I guess I can base all my decisions on size ;) -Steve

It would be very cool to have interfaces (and maybe polymorphism) for structs also. By the way: My intention was not to start a new flame war about the implementation of something like const&. There are enough topics and discussions here about that, and I'm sure, that in the next few years a few more will be added. My intention was to hear other opinions / answers to my questions. So I'm hoping that other express their opinion, too. At least I have noticed so far, that you're in any case for something like const&. Accordingly, you answer my question with 'yes, we need something like this.'.
Mar 01 2013
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 01 Mar 2013 18:35:43 -0500, Namespace <rswhite4 googlemail.com>  
wrote:

 At least I have noticed so far, that you're in any case for something  
 like const&. Accordingly, you answer my question with 'yes, we need  
 something like this.'.

I would say yes, we need something like rvalue references to avoid copy-paste hell. const& is not a good way to describe it, because it implies const, which this problem does not require. This is the major problem that Andrei had with it (at least as I understand his past statements) -- it conflates const with rvalue references. Sometimes, you want a const ref that does NOT bind to an rvalue. The one huge problem I've had with lack of rvalue references is with arithmetic operators: struct M { M opAdd(const ref M other) const {...} } M m; auto m2 = (m + m) + m; // ok! auto m3 = m + (m + m); // error! This is crap. -Steve
Mar 01 2013
prev sibling next sibling parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
 On Fri, 01 Mar 2013 18:35:43 -0500, Namespace 
 <rswhite4 googlemail.com> wrote:
 At least I have noticed so far, that you're in any case for 
 something like const&. Accordingly, you answer my question 
 with 'yes, we need something like this.'.


On Friday, 1 March 2013 at 23:46:36 UTC, Steven Schveighoffer wrote:
 I would say yes, we need something like rvalue references to 
 avoid copy-paste hell.  const& is not a good way to describe 
 it, because it implies const, which this problem does not 
 require.

 This is the major problem that Andrei had with it (at least as 
 I understand his past statements) -- it conflates const with 
 rvalue references.  Sometimes, you want a const ref that does 
 NOT bind to an rvalue.

If I have a medium sized struct that I only intend to read/reference from I see no reason not to use 'const ref' for simplicity and speed (and avoid postblit hopefully) but when it's an Rvalue I need to make a second function either duplicate except signature or forwarding the value.
 The one huge problem I've had with lack of rvalue references is 
 with arithmetic operators:

 struct M
 {
   M opAdd(const ref M other) const {...}
 }

 M m;

 auto m2 = (m + m) + m; // ok!
 auto m3 = m + (m + m); // error!

 This is crap.

If 'auto ref' gets accepted for non-template functions, it goes away. With M as you show, returning ref doesn't work so that example I was going to suggest doesn't work.
Mar 01 2013
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 01 Mar 2013 19:49:54 -0500, Era Scarecrow <rtcvb32 yahoo.com>  
wrote:

 On Friday, 1 March 2013 at 23:46:36 UTC, Steven Schveighoffer wrote:

 This is the major problem that Andrei had with it (at least as I  
 understand his past statements) -- it conflates const with rvalue  
 references.  Sometimes, you want a const ref that does NOT bind to an  
 rvalue.

If I have a medium sized struct that I only intend to read/reference from I see no reason not to use 'const ref' for simplicity and speed (and avoid postblit hopefully) but when it's an Rvalue I need to make a second function either duplicate except signature or forwarding the value.

The point is simple: foo(ref M m) foo(M m) An lvalue, yes, we want that to bind to ref. But an rvalue? I want that to bind to foo(M m), otherwise I would not have added that method. The by-value version is *more efficient* than the by-ref version with rvalues, even for large structs. But what if I ALSO want to say that foo doesn't change m? Well, that's easy! I just do: foo(const ref M m) But this makes rvalues bind to that version too! This is the problem. I want it to be ref, to avoid copies of a larg struct, and I want it to be const, for contract purposes, I DIDN'T want it to accept rvalues. The point is, there is a legitimate reason to mark a parameter const ref BESIDES wanting to have it bind to rvalues.
 The one huge problem I've had with lack of rvalue references is with  
 arithmetic operators:

 struct M
 {
   M opAdd(const ref M other) const {...}
 }

 M m;

 auto m2 = (m + m) + m; // ok!
 auto m3 = m + (m + m); // error!

 This is crap.

If 'auto ref' gets accepted for non-template functions, it goes away. With M as you show, returning ref doesn't work so that example I was going to suggest doesn't work.

In my code base, I have actual comments that explain why I have ordered certain operations the way I did! But we have more problems than just rvalue references. The compiler doesn't "see through" structs to know whether something is an lvalue or an rvalue. Consider writing your own pointer type: struct T { int *x; void opUnary(string op)() if (op == "++") {++(*x);} } int x; T foo() { return T(&x); } void main() { auto t = foo; t++; // ok foo++; // Error: foo() is not an lvalue } That should not be an error, or I should at least be able to tell the compiler "this is NOT an rvalue, even if it seems like one". -Steve
Mar 01 2013
prev sibling next sibling parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Saturday, 2 March 2013 at 01:15:20 UTC, Steven Schveighoffer 
wrote:
 The point is simple:

 foo(ref M m)
 foo(M m)

 An lvalue, yes, we want that to bind to ref.

 But an rvalue?  I want that to bind to foo(M m), otherwise I 
 would not have added that method.  The by-value version is 
 *more efficient* than the by-ref version with rvalues, even for 
 large structs.

 But what if I ALSO want to say that foo doesn't change m?  
 Well, that's easy!  I just do:

 foo(const ref M m)

 But this makes rvalues bind to that version too!  This is the 
 problem.  I want it to be ref, to avoid copies of a large 
 struct, and I want it to be const, for contract purposes, I 
 DIDN'T want it to accept rvalues.

I know, that's what 'auto ref' would be for.
 The point is, there is a legitimate reason to Mark a parameter 
 const ref BESIDES wanting to have it bind to rvalues.

I'm aware of that, sorry if I gave the wrong impression, but what I see in most of my cases is what I was referring to. We just got a similar problem where you have mutable data and immutable date, where there we have const as a middle ground that accepts both. We don't currently have the same thing for referencing. There are important reasons for having something referenced and having them as Rvalues, but when you don't care or need the middle ground is missing here.
 In my code base, I have actual comments that explain why I have 
 ordered certain operations the way I did!

 But we have more problems than just rvalue references.  The 
 compiler doesn't "see through" structs to know whether 
 something is an lvalue or an rvalue.

 Consider writing your own pointer type:

 struct T
 {
     int *x;
     void opUnary(string op)() if (op == "++") {++(*x);}
 }

 int x;

 T foo()
 {
     return T(&x);
 }

 void main()
 {
     auto t = foo;
     t++; // ok
     foo++; // Error: foo() is not an lvalue
 }

 That should not be an error, or I should at least be able to 
 tell the compiler "this is NOT an rvalue, even if it seems like 
 one".

No, foo returns an lValue, however the ++ is likely wrong. The compiler could have an ingrained belief that ++ is an assignment operator meaning it requires an Lvalue, at which point the code is wrong. But ++ is just an operator for the struct, so I don't know.
Mar 01 2013
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 01 Mar 2013 20:37:26 -0500, Era Scarecrow <rtcvb32 yahoo.com>  
wrote:

 On Saturday, 2 March 2013 at 01:15:20 UTC, Steven Schveighoffer wrote:
 void main()
 {
     auto t = foo;
     t++; // ok
     foo++; // Error: foo() is not an lvalue
 }

 That should not be an error, or I should at least be able to tell the  
 compiler "this is NOT an rvalue, even if it seems like one".

No, foo returns an lValue, however the ++ is likely wrong. The compiler could have an ingrained belief that ++ is an assignment operator meaning it requires an Lvalue, at which point the code is wrong. But ++ is just an operator for the struct, so I don't know.

You are saying opposite things "foo returns an LValue" "it requires an Lvalue, at which point the code is wrong" Which is it? I contend that T is an LValue type. If the compiler can't see that, it needs to be told, or it needs to get out of the way. I just tested a simple "incx()" function that is identical to the operator, and it works. I can even call the operator via foo.opUnary!"++"() and it works! So the compiler is restricting things that are trivially circumvented. Maybe it should be: calling a method of an rvalue, even if that method is called via operator overload, should always be allowed. Any simple field access should fail if the rvalue is modified. So for instance foo.x++ should fail, but foo++ should work, even if all it does is x++. Maybe if the compiler inlines the code and sees it trivially reduces to a rejected case, it can reject it. -Steve
Mar 01 2013
prev sibling next sibling parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Saturday, 2 March 2013 at 01:51:28 UTC, Steven Schveighoffer 
wrote:
 On Fri, 01 Mar 2013 20:37:26 -0500, Era Scarecrow 
 <rtcvb32 yahoo.com> wrote:

 On Saturday, 2 March 2013 at 01:15:20 UTC, Steven 
 Schveighoffer wrote:
 void main()
 {
    auto t = foo;
    t++; // ok
    foo++; // Error: foo() is not an lvalue
 }

 That should not be an error, or I should at least be able to 
 tell the compiler "this is NOT an rvalue, even if it seems 
 like one".

No, foo returns an lValue, however the ++ is likely wrong. The compiler could have an ingrained belief that ++ is an assignment operator meaning it requires an Lvalue, at which point the code is wrong. But ++ is just an operator for the struct, so I don't know.

You are saying opposite things "foo returns an LValue" "it requires an Lvalue, at which point the code is wrong"

Sorry I hate typos. foo returns an Rvalue, therefore is an Rvalue.
 I contend that T is an LValue type.  If the compiler can't see 
 that, it needs to be told, or it needs to get out of the way.

 I just tested a simple "incx()" function that is identical to 
 the operator, and it works.  I can even call the operator via 
 foo.opUnary!"++"() and it works!  So the compiler is 
 restricting things that are trivially circumvented.

Anyways, because of how ++ and -- are assumed to basically also be opAssign it requires an LValue, at least that's what the compiler sees, and for simplicity it should probably continue to follow that. Calling it explicitly removes the assumption it requires an Lvalue. If it's const then calling it could be safe as the object/temporary/Rvalue never changes. Hard to say.
 Maybe it should be: calling a method of an rvalue, even if that 
 method is called via operator overload, should always be 
 allowed.  Any simple field access should fail if the rvalue is 
 modified.  So for instance foo.x++ should fail, but foo++ 
 should work, even if all it does is x++.  Maybe if the compiler 
 inlines the code and sees it trivially reduces to a rejected 
 case, it can reject it.

Mar 01 2013
prev sibling next sibling parent "Namespace" <rswhite4 googlemail.com> writes:
Era Scarecrow:
Did I understand right that your answer is 'No, we don't need 
something like const&'?
Mar 02 2013
prev sibling next sibling parent "Namespace" <rswhite4 googlemail.com> writes:
On Saturday, 2 March 2013 at 00:49:55 UTC, Era Scarecrow wrote:
 If 'auto ref' gets accepted for non-template functions, it goes 
 away. With M as you show, returning ref doesn't work so that 
 example I was going to suggest doesn't work.

auto ref will in all probability never work for non-template functions, as I said before. Steven Schveighoffer: I understand, that you like to have rvalue references for structs but I still don't understand why. Despite yesterday's discussion, I still do not understand the reason to use a struct instead of a class, when it comes to a massive amount of data. structs are copied or moved, structs have no polymorphism and no interfaces, so why you should use a struct instead of a class, if your struct is very massive? I don't get it.
Mar 02 2013
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Saturday, 2 March 2013 at 08:56:04 UTC, Namespace wrote:
 On Saturday, 2 March 2013 at 00:49:55 UTC, Era Scarecrow wrote:
 If 'auto ref' gets accepted for non-template functions, it 
 goes away. With M as you show, returning ref doesn't work so 
 that example I was going to suggest doesn't work.

auto ref will in all probability never work for non-template functions, as I said before. Steven Schveighoffer: I understand, that you like to have rvalue references for structs but I still don't understand why. Despite yesterday's discussion, I still do not understand the reason to use a struct instead of a class, when it comes to a massive amount of data. structs are copied or moved, structs have no polymorphism and no interfaces, so why you should use a struct instead of a class, if your struct is very massive? I don't get it.

1/ Generic code. You may not know that the data are big after starting conglomerating more and more stuff. 2/ Data may be small, but with an expensive copy mecanism. 3/ A class would require to create the proper routines to duplicate itself. 4/ Classes imply indirections. Which may be a problem (think about array of such item for instance). 5/ Value type have great benefice in regard to the GC. LRU cache is a very good way to kill Java's GC, because it generate plenty of old garbages. The same thing in C# using value types don't cause that much GC trashing. And I can go on and on.
Mar 02 2013
prev sibling next sibling parent "Namespace" <rswhite4 googlemail.com> writes:
 1/ Generic code. You may not know that the data are big after 
 starting conglomerating more and more stuff.

 2/ Data may be small, but with an expensive copy mecanism.

 And I can go on and on.

Mar 02 2013
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Saturday, 2 March 2013 at 09:31:56 UTC, Namespace wrote:
 1/ Generic code. You may not know that the data are big after 
 starting conglomerating more and more stuff.


Passing by ref small struct for nothing is also a performance concern. You'll access them via dereference when you could have them directly in registers, and reduce the compiler capability of doing optimization based on aliasing. Auto ref is convenient, but look more like an ugly patch than a real solution.
 2/ Data may be small, but with an expensive copy mecanism.


struct ValueArray(T) { T[] data; alias this = data; // Damned, it is broken on 2.062 this(this) { data = data.dup; } }
 And I can go on and on.


I don't see the point of providing more data when you ignored half of what I provided and misunderstood the other half.
Mar 02 2013
prev sibling next sibling parent "Namespace" <rswhite4 googlemail.com> writes:
Excuse me if I misunderstood you, this happens, especially if 
English is not the native language.
But you aren't talking only with me, but I want to hear your full 
opinion.
And if I ignore something, then I have nothing say, because I 
have no arguments.
Say you can take it as tacit consent.
Mar 02 2013
prev sibling next sibling parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Saturday, 2 March 2013 at 08:29:25 UTC, Namespace wrote:
 Era Scarecrow:
 Did I understand right that your answer is 'No, we don't need 
 something like const&'?

const& is ugly and suggests it is using a pointer which we don't want to use except in low level stuff. const& doesn't make sense, but 'auto ref' does, and I think it could be quite useful. On Saturday, 2 March 2013 at 09:38:17 UTC, deadalnix wrote:
 On Saturday, 2 March 2013 at 09:31:56 UTC, Namespace wrote:
 On Saturday, 2 March 2013 at 09:38:17 UTC, deadalnix wrote:
 1/ Generic code. You may not know that the data are big after 
 starting conglomerating more and more stuff.


Passing by ref small struct for nothing is also a performance concern. You'll access them via dereference when you could have them directly in registers, and reduce the compiler capability of doing optimization based on aliasing. Auto ref is convenient, but look more like an ugly patch than a real solution.

And if it's not implemented I'll be doomed making the same forwarding functions due to ref & const preference rules. I can live with it but I don't want to. I'm not saying I'll use auto ref for everything, only when it makes sense to.
 2/ Data may be small, but with an expensive copy mechanism.


struct ValueArray(T) { T[] data; alias this = data; // Damned, it is broken on 2.062 this(this) { data = data.dup; } }

Perhaps enlarge the example just a touch... alias ValueArray!ubyte BigUbArray; BigUbArray array = new ubyte[1<<20]; //1 meg static assert(BigUbArray.sizeof <= 16); //gee 'BigUbArray' is only 8-16 bytes!!! postblit! void func(BigUbArray s); If it's POD however then it shouldn't have any modifications/postblit or anything special to worry about.
Mar 02 2013
prev sibling parent "Namespace" <rswhite4 googlemail.com> writes:
On Saturday, 2 March 2013 at 21:46:49 UTC, Era Scarecrow wrote:
 On Saturday, 2 March 2013 at 08:29:25 UTC, Namespace wrote:
 Era Scarecrow:
 Did I understand right that your answer is 'No, we don't need 
 something like const&'?

const& is ugly and suggests it is using a pointer which we don't want to use except in low level stuff. const& doesn't make sense, but 'auto ref' does, and I think it could be quite useful.

Yes, something similar to "auto ref" was meant by "_something_ like const&", of course. ;)
Mar 02 2013