digitalmars.D - Does D really need something like const&?
- Namespace (23/23) Mar 01 2013 Currently small structs are moved as rvalues and copied as
- Steven Schveighoffer (14/16) Mar 01 2013 Size is not a major factor when I decide whether to use a struct or
- Namespace (13/16) Mar 01 2013 Size is next polymorphism my main reason why I would use a class
- Steven Schveighoffer (33/49) Mar 01 2013 Features of classes vs. structs. Need an interface? class. Need fine ...
- Namespace (6/7) Mar 01 2013 scope and scoped give you the possibility to put a class instance
- Steven Schveighoffer (9/15) Mar 01 2013 Nice try? I don't get this. It was supposed to be the analogue to C++ ...
- Namespace (18/28) Mar 01 2013 I got the answer that 'auto ref' is not the solution which will
- Steven Schveighoffer (20/23) Mar 01 2013 I would say yes, we need something like rvalue references to avoid
- Era Scarecrow (10/33) Mar 01 2013 If I have a medium sized struct that I only intend to
- Steven Schveighoffer (42/69) Mar 01 2013 The point is simple:
- Era Scarecrow (17/59) Mar 01 2013 I know, that's what 'auto ref' would be for.
- Steven Schveighoffer (18/32) Mar 01 2013 You are saying opposite things "foo returns an LValue" "it requires an ...
- Era Scarecrow (10/45) Mar 01 2013 Sorry I hate typos. foo returns an Rvalue, therefore is an
- Namespace (3/3) Mar 02 2013 Era Scarecrow:
- Namespace (12/15) Mar 02 2013 auto ref will in all probability never work for non-template
- deadalnix (13/28) Mar 02 2013 1/ Generic code. You may not know that the data are big after
- Namespace (3/7) Mar 02 2013 Yes go on, I'd like to hear more.
- deadalnix (16/23) Mar 02 2013 Passing by ref small struct for nothing is also a performance
- Namespace (7/7) Mar 02 2013 Excuse me if I misunderstood you, this happens, especially if
- Era Scarecrow (17/40) Mar 02 2013 const& is ugly and suggests it is using a pointer which we don't
- Namespace (3/11) Mar 02 2013 Yes, something similar to "auto ref" was meant by "_something_
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
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
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
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.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. -SteveAnd 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
I know about 'auto ref'. Nice try but ... ;)Need fine control over lifetime?scope and scoped give you the possibility to put a class instance 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
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.scoped is implemented via a struct...Need fine control over lifetime?scope and scoped give you the possibility to put a class instance 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.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
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...Whatever. The fact is that you can put a class instance on the stack and controll the instance lifetime also.:D My statement was ironic. In this I am not so good, sorry. 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.'.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
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
On Fri, 01 Mar 2013 18:35:43 -0500, Namespace <rswhite4 googlemail.com> wrote:On Friday, 1 March 2013 at 23:46:36 UTC, Steven Schveighoffer 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.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
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: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.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.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". -SteveThe 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
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
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: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. -Stevevoid 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
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:Sorry I hate typos. foo returns an Rvalue, therefore is an Rvalue.On Saturday, 2 March 2013 at 01:15:20 UTC, Steven Schveighoffer wrote:You are saying opposite things "foo returns an LValue" "it requires an Lvalue, at which point the code is wrong"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.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
Era Scarecrow: Did I understand right that your answer is 'No, we don't need something like const&'?
Mar 02 2013
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
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: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 cause that much GC trashing. And I can go on and on.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
1/ Generic code. You may not know that the data are big after starting conglomerating more and more stuff.auto ref for templates still exists and won't get away.2/ Data may be small, but with an expensive copy mecanism.Example?And I can go on and on.Yes go on, I'd like to hear more.
Mar 02 2013
On Saturday, 2 March 2013 at 09:31:56 UTC, Namespace wrote: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.1/ Generic code. You may not know that the data are big after starting conglomerating more and more stuff.auto ref for templates still exists and won't get away.struct ValueArray(T) { T[] data; alias this = data; // Damned, it is broken on 2.062 this(this) { data = data.dup; } }2/ Data may be small, but with an expensive copy mecanism.Example?I don't see the point of providing more data when you ignored half of what I provided and misunderstood the other half.And I can go on and on.Yes go on, I'd like to hear more.
Mar 02 2013
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
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: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.On Saturday, 2 March 2013 at 09:38:17 UTC, deadalnix wrote: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.1/ Generic code. You may not know that the data are big after starting conglomerating more and more stuff.auto ref for templates still exists and won't get away.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.struct ValueArray(T) { T[] data; alias this = data; // Damned, it is broken on 2.062 this(this) { data = data.dup; } }2/ Data may be small, but with an expensive copy mechanism.Example?
Mar 02 2013
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:Yes, something similar to "auto ref" was meant by "_something_ like const&", of course. ;)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.
Mar 02 2013