digitalmars.D - Destructors, const structs, and opEquals
- Don (32/32) Dec 03 2010 Officially, opEquals has to have the signature:
- Franciszek Czekala (21/50) Dec 04 2010 seems a little nonsensical. And you cannot have both const and non-
- spir (20/27) Dec 04 2010 I support this point of view. In general, I think properly chosen qualif...
- Olivier Pisano (11/18) Dec 05 2010 Hi,
- Jonathan M Davis (8/9) Dec 04 2010 I honestly do not understand why they can't be already. C++ definitely a...
- so (4/9) Dec 04 2010 If you don't mean new C++ standards, this is not true. It is supported b...
- Jonathan M Davis (7/16) Dec 04 2010 I'm 99.99% certain that it's perfectly legal to pass a temporary to a fu...
- so (11/14) Dec 04 2010 Oh that is right, but both are different things.
- Andrei Alexandrescu (5/17) Dec 04 2010 Second line is legal too. Petru Marginean and I use it to good effect in...
- so (11/30) Dec 04 2010 I was sure that always output C4238 on MSVC, and simply rejected on GCC.
- so (4/5) Dec 04 2010 Should be:
- Andrei Alexandrescu (3/7) Dec 04 2010 C++'s second biggest mistake.
- Jonathan M Davis (6/14) Dec 04 2010 Okay. Why is it a mistake? I've never heard anyone say that this was a m...
- Andrei Alexandrescu (3/16) Dec 04 2010 It makes it impossible to distinguish an rvalue from an lvalue.
- so (5/7) Dec 04 2010 Rather, what keeps compiler from interpreting "A fun()" like "const ref ...
- so (6/8) Dec 04 2010 Oh... we are saying exact same thing but i interpreted "passed as 'const...
- Andrei Alexandrescu (28/60) Dec 04 2010 This is a compiler bug. For structs there should be no official
- Don (26/98) Dec 04 2010 Fine, but try to implement a generic type which supports ==.
- Andrei Alexandrescu (38/138) Dec 04 2010 I think it should be as follows:
- so (8/16) Dec 04 2010 Const-system is a one big abomination, considering the consequences it i...
- Andrej Mitrovic (4/21) Dec 04 2010 o
- Don (12/174) Dec 04 2010 Ouch.
- Andrei Alexandrescu (20/74) Dec 04 2010 It's not the complexity of the object as much as "don't pay for const if...
- Steven Schveighoffer (10/78) Dec 04 2010 You have not addressed that problem -- tack an inout on it, everybody mu...
- Andrei Alexandrescu (5/85) Dec 04 2010 I don't think so. On the contrary, declaring with inout is the most
- Steven Schveighoffer (5/6) Dec 04 2010 I responded better in another part of this thread.
- Andrei Alexandrescu (6/11) Dec 05 2010 Oh, I see. I thought its behavior can be equivalent to producing three
- Steven Schveighoffer (23/57) Dec 04 2010 No no no, inout does not belong here. Use const. inout is only used if...
- Andrei Alexandrescu (9/70) Dec 04 2010 No.
- Andrei Alexandrescu (3/5) Dec 04 2010 Sorry, I meant: method calls are already allowed for rvalues. Sleepy...
- Steven Schveighoffer (24/85) Dec 04 2010 Huh? I don't think you understand what I mean. inout only implicitly
- so (6/23) Dec 05 2010 You are right. Reading it again, using inout is wrong here, both as
- Andrei Alexandrescu (13/18) Dec 05 2010 Because you sometimes do care about dealing with a true lvalue. Consider...
- Steven Schveighoffer (12/29) Dec 06 2010 Right.
- Steven Schveighoffer (4/37) Dec 10 2010 Not sure if this got lost in the noise, I'm still puzzled about this...
- Andrei Alexandrescu (21/61) Dec 10 2010 Sorry, indeed I haven't seen it.
- Steven Schveighoffer (11/74) Dec 10 2010 OK, now I get it, thanks for explaining it again :) So essentially you ...
- Andrei Alexandrescu (9/87) Dec 10 2010 That is correct. There are a couple of related bug reports
- Bruno Medeiros (14/17) Dec 21 2010 I was reminded of another comment that could be said in favor of that:
- Don (2/6) Dec 10 2010 That use of 'auto' is an abomination.
- foobar (7/14) Dec 10 2010 I agree with don.
- Andrei Alexandrescu (4/16) Dec 10 2010 Everyone - please stop suggesting that. It causes severe undue aliasing
- Michel Fortin (37/55) Dec 11 2010 If I understand you well Andrei, what you want "auto ref" to be the
- spir (13/73) Dec 11 2010 y ref.
- Michel Fortin (24/28) Dec 11 2010 No it can't because of aliasing (the object might be referenced by
- so (9/14) Dec 11 2010 This kind of design decisions, leaving this responsibility to compiler i...
- Don (21/43) Dec 13 2010 I don't understand this.
- Andrei Alexandrescu (8/51) Dec 13 2010 I agree we should ideally do better than that. The problem with the
- Don (12/78) Dec 13 2010 I can't really escape the feeling that 'const' guarantees too little.
- Jesse Phillips (3/8) Dec 13 2010 I agree here. Makes it seem like you should also have 'auto immutable' a...
- Don (23/36) Dec 14 2010 It says, "you are not allowed to modify this". But it doesn't tell you
- Michel Fortin (20/23) Dec 13 2010 I don't like "auto ref" as a syntax either, but I also dislike the
- Brad Roberts (5/7) Dec 13 2010 As far as I'm concerned, that's exactly what I want const for. The call...
- Don (2/12) Dec 14 2010 Yes. But the callee wants some guarantees as well. How can we provide th...
- Steven Schveighoffer (10/12) Dec 14 2010 This is the basis of my argument that adding logical const would not
- so (9/19) Dec 10 2010 "auto ref" as a syntax may be not the best choice but the way it solves ...
- Michel Fortin (28/34) Dec 10 2010 One problem I'm starting to realize is that we now have so many
- Andrei Alexandrescu (4/33) Dec 10 2010 It's sort of ironic. You just argued for the utility of, and
- Michel Fortin (21/50) Dec 10 2010 Yeah, I know it's a little ironic. There's a difference though.
- spir (17/53) Dec 11 2010 uld be:
- Andrei Alexandrescu (30/72) Dec 11 2010 The concern is valid. On the other hand, D is in the business of
- Andrej Mitrovic (15/18) Dec 11 2010 Nothing. Keep D2 as it is. We have to use D2 for a few years and build
- spir (9/11) Dec 11 2010 The need for yet another one signifie s=C3=BBrement (probably means) the...
- Andrei Alexandrescu (5/10) Dec 11 2010 I'm all for it. Even if we can't change the language, I'd like to know
- Don (7/43) Dec 11 2010 The problem is that 'auto' in 'auto ref' has *a contradictory meaning*
- Max Samukha (5/69) Dec 10 2010 Thanks a lot for taking time to explain!
- Andrej Mitrovic (1/5) Dec 10 2010 Thanks, I had trouble understanding what this whole rvalue deal is all a...
- Andrej Mitrovic (2/9) Dec 10 2010 Ah, see, that article talks about RVO - Walter's cool optimization techn...
- Andrej Mitrovic (3/15) Dec 10 2010 P.S. The second link on that page is dead, but I've found a direct
- Andrej Mitrovic (5/17) Dec 10 2010 I hate to spam this topic (last post, I swear), but I find it amusing
- Bruno Medeiros (19/45) Dec 21 2010 For some cases, one could just use a differently-named function, instead...
- spir (10/17) Dec 05 2010 =20
- kenji hara (10/78) Dec 04 2010 Andrei, your explanation is almost the same as was my understanding. Tha...
Officially, opEquals has to have the signature: struct Foo { bool opEquals(const ref Foo x) const {...} } But this disallows comparisons with rvalues. eg, Foo bar() { Foo x = 1; return x; } Foo y=1; assert( y == bar() ); // doesn't compile You can get around this by declaring a non-ref opEquals. But this fails if Foo has a destructor. If a struct has a destructor, it cannot be const(this is bug 3606) --- struct S { ~this() {} } void main() { const S z; } --- bug.d(6): Error: destructor bug.S.~this () is not callable using argument types () ------- Likewise, it can't be a const parameter (this is bug 4338). void foo(const S a) {} It works to have it as a const ref parameter. Everything will work if you declare a const ~this(), but that seems a little nonsensical. And you cannot have both const and non-const ~this(). I'm a bit uncertain as to how this is all supposed to work. (1) Should temporaries be allowed to be passed as 'const ref'? (2) If a struct has a destructor, should it be passable as a const parameter? And if so, should the destructor be called?
Dec 03 2010
Officially, opEquals has to have the signature: struct Foo { bool opEquals(const ref Foo x) const {...} } But this disallows comparisons with rvalues. eg, Foo bar() { Foo x = 1; return x; } Foo y=1; assert( y == bar() ); // doesn't compile You can get around this by declaring a non-ref opEquals. But this fails if Foo has a destructor. If a struct has a destructor, it cannot be const(this is bug3606)--- struct S { ~this() {} } void main() { const S z; } --- bug.d(6): Error: destructor bug.S.~this () is not callable usingargument types ()------- Likewise, it can't be a const parameter (this is bug 4338). void foo(const S a) {} It works to have it as a const ref parameter. Everything will work if you declare a const ~this(), but thatseems a little nonsensical. And you cannot have both const and non- const ~this().I'm a bit uncertain as to how this is all supposed to work. (1) Should temporaries be allowed to be passed as 'const ref'? (2) If a struct has a destructor, should it be passable as aconst parameter? And if so, should the destructor be called?I was wondering. Why cannot things be done simply? In Ada95 one has: function "=" (X,Y: in T) return Boolean; for every type T, simple or composite, records and classed included. Ada95 has been around for 15 years and did not gain any popularity even though it was described as better than C++. I wish D all best, but in view of the problem signaled in this post the prospects are dim. D seems just a bit too complicated for a compelling replacement of existing languages. Anyway, if struct has value semantics then perhaps the argument to opEquals should have simply 'in' mode? In Ada95 the 'in' mode of the arguments does not determine how the arguments are passed internally to the function. The compiler can choose to pass them by value or by reference as suitable. Since the 'in' mode makes the arguments constant inside, it does not really matter how the arguments are passed, so why burden the user with this knowledge?
Dec 04 2010
On Sat, 4 Dec 2010 09:00:05 +0000 (UTC) Franciszek Czekala <home valentimex.com> wrote:Anyway, if struct has value semantics then perhaps the argument to opEquals should have simply 'in' mode? In Ada95 the 'in' mode of the arguments does not determine how the arguments are passed internally to the function. The compiler can choose to pass them by value or by reference as suitable. Since the 'in' mode makes the arguments constant inside, it does not really matter how the arguments are passed, so why burden the user with this knowledge?I support this point of view. In general, I think properly chosen qualifier= s should have clear semantics (meaning); then, the language internally may = adopt what is best for simplicity, efficiency, etc... as long as semantics = are maintained. A programmer should not have to care about it (what is wron= g for instance about arrays). Semantics and implementation may be much more= kept apart in D. What is the sense of "in" for an app designer? For the case of "in" (with a value argument), as the argument does not need= to be protected by copy since it is known not to change, the compiler may = chose to pass it by ref when more efficient (structs, mostly). Moreover, wh= y not have "in" be the default for values? What is the meaning of changing = a value parameter?=20 draw (shape, color, position); What is the sense of changing color or position? Qualifiers (semantics in g= eneral) could make more sense. Denis -- -- -- -- -- -- -- vit esse estrany =E2=98=A3 spir.wikidot.com
Dec 04 2010
Le 04/12/2010 10:00, Franciszek Czekala a écrit :Anyway, if struct has value semantics then perhaps the argument to opEquals should have simply 'in' mode? In Ada95 the 'in' mode of the arguments does not determine how the arguments are passed internally to the function. The compiler can choose to pass them by value or by reference as suitable. Since the 'in' mode makes the arguments constant inside, it does not really matter how the arguments are passed, so why burden the user with this knowledge?Hi, I am certainly not expert enough to estimate what the consequences such a change would be for the existing code base, but I really appreciate this idea. I suppose a potential problem would be when trying to link code written by different compilers together. In such a scenario, "how the arguments are passed" matters. But I don't know any bit of Ada and don't have a clue about they solved this issue. Cheers, Olivier.
Dec 05 2010
On Friday 03 December 2010 22:42:06 Don wrote:(1) Should temporaries be allowed to be passed as 'const ref'?I honestly do not understand why they can't be already. C++ definitely allows this. Is there something bad about it? I'd probably use const ref a lot more, but because it will only take lvalues, it's _highly_ limiting. If you could overload functions on ref (I _think_ that there's a bug on that), then you could have two versions of opEquals - with with const ref and one which would copy the value - but ideally, you'd only need the one with const ref. - Jonathan M Davis
Dec 04 2010
On Friday 03 December 2010 22:42:06 Don wrote:If you don't mean new C++ standards, this is not true. It is supported by non-standard extensions. -- Using Opera's revolutionary email client: http://www.opera.com/mail/(1) Should temporaries be allowed to be passed as 'const ref'?I honestly do not understand why they can't be already. C++ definitely allows this.
Dec 04 2010
On Saturday 04 December 2010 03:49:26 so wrote:I'm 99.99% certain that it's perfectly legal to pass a temporary to a function that takes a const T& and that it's in the standard. I'm fairly certain that I've read it in at least one book (though I'd have to look it up to be sure), but regardless, both gcc and Visual Studio definitely allow it, so if it's non- standard, it's still highly supported. - Jonathan M DavisOn Friday 03 December 2010 22:42:06 Don wrote:If you don't mean new C++ standards, this is not true. It is supported by non-standard extensions.(1) Should temporaries be allowed to be passed as 'const ref'?I honestly do not understand why they can't be already. C++ definitely allows this.
Dec 04 2010
I'm 99.99% certain that it's perfectly legal to pass a temporary to a function that takes a const T& and that it's in the standardOh that is right, but both are different things. Say, when you have: T fun() {...} void bar(const T&) {...} bar(fun()) // 1. this is perfectly legal. const T& a = fun(); // 2. not legal, but still you can do it on some compilers. What Don's example is all about as far as i can tell is that D can't do the first one, somehow. -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 04 2010
On 12/4/10 6:50 AM, so wrote:Second line is legal too. Petru Marginean and I use it to good effect in our ScopeGuard idiom (a precursor to D's scope guards). http://www.drdobbs.com/184403758 AndreiI'm 99.99% certain that it's perfectly legal to pass a temporary to a function that takes a const T& and that it's in the standardOh that is right, but both are different things. Say, when you have: T fun() {...} void bar(const T&) {...} bar(fun()) // 1. this is perfectly legal. const T& a = fun(); // 2. not legal, but still you can do it on some compilers. What Don's example is all about as far as i can tell is that D can't do the first one, somehow.
Dec 04 2010
On Sat, 04 Dec 2010 16:05:07 +0200, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:On 12/4/10 6:50 AM, so wrote:I was sure that always output C4238 on MSVC, and simply rejected on GCC. Now I tried with 2 versions of MSVC and it didn't give any warnings. As it looks like this is only for pointers. That is: const T* a = &fun(); I have encountered this quite a few times and i was sure reference example above also same since i can't think of a reason that i would take take the address of a temporary function... -- Using Opera's revolutionary email client: http://www.opera.com/mail/Second line is legal too. Petru Marginean and I use it to good effect in my ScopeGuard idiom (a precursor to D's scope guards). http://www.drdobbs.com/184403758 AndreiI'm 99.99% certain that it's perfectly legal to pass a temporary to a function that takes a const T& and that it's in the standardOh that is right, but both are different things. Say, when you have: T fun() {...} void bar(const T&) {...} bar(fun()) // 1. this is perfectly legal. const T& a = fun(); // 2. not legal, but still you can do it on some compilers.
Dec 04 2010
i would take take the address of a temporary function...Should be: i would take the address of a temporary object/value. -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 04 2010
On 12/4/10 4:35 AM, Jonathan M Davis wrote:On Friday 03 December 2010 22:42:06 Don wrote:C++'s second biggest mistake. Andrei(1) Should temporaries be allowed to be passed as 'const ref'?I honestly do not understand why they can't be already. C++ definitely allows this.
Dec 04 2010
On Saturday 04 December 2010 06:00:58 Andrei Alexandrescu wrote:On 12/4/10 4:35 AM, Jonathan M Davis wrote:Okay. Why is it a mistake? I've never heard anyone say that this was a mistake before. As far as I can tell, it's extremely desirable, and this problem with opEquals() is a prime example as to why. What is wrong with allowing temporaries to be passed as const ref? What makes it such a big mistake? - Jonathan M DavisOn Friday 03 December 2010 22:42:06 Don wrote:C++'s second biggest mistake.(1) Should temporaries be allowed to be passed as 'const ref'?I honestly do not understand why they can't be already. C++ definitely allows this.
Dec 04 2010
On 12/4/10 8:29 AM, Jonathan M Davis wrote:On Saturday 04 December 2010 06:00:58 Andrei Alexandrescu wrote:It makes it impossible to distinguish an rvalue from an lvalue. AndreiOn 12/4/10 4:35 AM, Jonathan M Davis wrote:Okay. Why is it a mistake? I've never heard anyone say that this was a mistake before. As far as I can tell, it's extremely desirable, and this problem with opEquals() is a prime example as to why. What is wrong with allowing temporaries to be passed as const ref? What makes it such a big mistake?On Friday 03 December 2010 22:42:06 Don wrote:C++'s second biggest mistake.(1) Should temporaries be allowed to be passed as 'const ref'?I honestly do not understand why they can't be already. C++ definitely allows this.
Dec 04 2010
I'm a bit uncertain as to how this is all supposed to work. (1) Should temporaries be allowed to be passed as 'const ref'?Rather, what keeps compiler from interpreting "A fun()" like "const ref A fun()" when it is necessary? After all D's const system has no holes in this case unlike C++. -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 04 2010
Oh... we are saying exact same thing but i interpreted "passed as 'const ref'" out of the context! I guess Jonathan also saying the same thing. Sorry about that! -- Using Opera's revolutionary email client: http://www.opera.com/mail/I'm a bit uncertain as to how this is all supposed to work. (1) Should temporaries be allowed to be passed as 'const ref'?
Dec 04 2010
On 12/4/10 12:42 AM, Don wrote:Officially, opEquals has to have the signature: struct Foo { bool opEquals(const ref Foo x) const {...} }This is a compiler bug. For structs there should be no official implementation of opEquals, opCmp etc. All the compiler needs to worry about is to syntactically translate a == b to a.opEquals(b) and then let the usual language rules resolve the call.But this disallows comparisons with rvalues. eg, Foo bar() { Foo x = 1; return x; } Foo y=1; assert( y == bar() ); // doesn't compile You can get around this by declaring a non-ref opEquals. But this fails if Foo has a destructor. If a struct has a destructor, it cannot be const(this is bug 3606) --- struct S { ~this() {} } void main() { const S z; } --- bug.d(6): Error: destructor bug.S.~this () is not callable using argument types () ------- Likewise, it can't be a const parameter (this is bug 4338). void foo(const S a) {} It works to have it as a const ref parameter. Everything will work if you declare a const ~this(), but that seems a little nonsensical. And you cannot have both const and non-const ~this(). I'm a bit uncertain as to how this is all supposed to work. (1) Should temporaries be allowed to be passed as 'const ref'? (2) If a struct has a destructor, should it be passable as a const parameter? And if so, should the destructor be called?This is a delicate matter that clearly needs a solution. Pass of temporaries by const ref was a huge mistake of C++ that it has paid dearly for and required the introduction of a large complication, the rvalue references feature, to just undo the effects of that mistake. So I don't think we should allow that. Regarding destructors, for every constructed object ever there must be a corresponding destructor call. One issue that has been a matter of debate in C++ has been the fact that any object becomes "deconstified" during destruction. The oddest consequence of that rule is that in C++ you can delete a pointer to a const object: // C++ code class A { ... }; void fun(const A* p) { delete p; /* fine */ } There has been a lot of opposition. const is supposed to limit what you can do with that object, and the fact that you can't invoke certain methods or change members, but you can nuke the entire object, is quite nonintuitive (and leads to a lot of funny real-life comparisons such as "You can go out with my daughter, but no touching. Of course, you can shoot her if you so wish.") In D, the rule must be inferred from D's immutability rules, which pretty much dictate that the destructor must be overloaded for non-const and const (and possibly invariant if the struct needs that). Andrei
Dec 04 2010
Andrei Alexandrescu wrote:On 12/4/10 12:42 AM, Don wrote:Fine, but try to implement a generic type which supports ==. For example, Tuple in std.typecons. The only reason that many of the Tuple unit tests pass is that opEquals never gets instantiated. How can opEquals be defined in a way that it works for structs with destructors, and also with rvalues?Officially, opEquals has to have the signature: struct Foo { bool opEquals(const ref Foo x) const {...} }This is a compiler bug. For structs there should be no official implementation of opEquals, opCmp etc. All the compiler needs to worry about is to syntactically translate a == b to a.opEquals(b) and then let the usual language rules resolve the call.This scares me. I can see a danger of making structs with destructors practically unusable. Some unprocessed thoughts are below. Who calls postblit? I think that if you can make a clone of a const object, you should also be able to destroy that clone. Seems to me that the natural rule would be, that the creator is responsible for destruction. This would require that, for example, given code like this: const(S) foo(const(S) x) { return x; } inside foo, a non-const S is created, blitted with x, then postblit is called on it, then it is returned as const. On return, the original x is destroyed if it was a temporary. Otherwise, it gets called at the end of its scope. Eg, const w = foo(foo( S(2) )); would be translated into: const w = (foo( S __tmp1 = S(2), const(S) __tmp2 = foo(__tmp1), ~__tmp1, __tmp2), ~__tmp2); Has this sort of idea been explored? Is there something wrong with it?But this disallows comparisons with rvalues. eg, Foo bar() { Foo x = 1; return x; } Foo y=1; assert( y == bar() ); // doesn't compile You can get around this by declaring a non-ref opEquals. But this fails if Foo has a destructor. If a struct has a destructor, it cannot be const(this is bug 3606) --- struct S { ~this() {} } void main() { const S z; } --- bug.d(6): Error: destructor bug.S.~this () is not callable using argument types () ------- Likewise, it can't be a const parameter (this is bug 4338). void foo(const S a) {} It works to have it as a const ref parameter. Everything will work if you declare a const ~this(), but that seems a little nonsensical. And you cannot have both const and non-const ~this(). I'm a bit uncertain as to how this is all supposed to work. (1) Should temporaries be allowed to be passed as 'const ref'? (2) If a struct has a destructor, should it be passable as a const parameter? And if so, should the destructor be called?This is a delicate matter that clearly needs a solution. Pass of temporaries by const ref was a huge mistake of C++ that it has paid dearly for and required the introduction of a large complication, the rvalue references feature, to just undo the effects of that mistake. So I don't think we should allow that. Regarding destructors, for every constructed object ever there must be a corresponding destructor call. One issue that has been a matter of debate in C++ has been the fact that any object becomes "deconstified" during destruction. The oddest consequence of that rule is that in C++ you can delete a pointer to a const object: // C++ code class A { ... }; void fun(const A* p) { delete p; /* fine */ } There has been a lot of opposition. const is supposed to limit what you can do with that object, and the fact that you can't invoke certain methods or change members, but you can nuke the entire object, is quite nonintuitive (and leads to a lot of funny real-life comparisons such as "You can go out with my daughter, but no touching. Of course, you can shoot her if you so wish.") In D, the rule must be inferred from D's immutability rules, which pretty much dictate that the destructor must be overloaded for non-const and const (and possibly invariant if the struct needs that).
Dec 04 2010
On 12/4/10 9:23 AM, Don wrote:Andrei Alexandrescu wrote:I think it should be as follows: bool opEquals(auto ref inout Tuple rhs) inout { foreach (i, T; Types) { if (this[i] != rhs[i]) return false; } return true; } It looks a bit alembicated but let's not forget that Tuple is supposed to be very flexible and to do a lot of things.On 12/4/10 12:42 AM, Don wrote:Fine, but try to implement a generic type which supports ==. For example, Tuple in std.typecons. The only reason that many of the Tuple unit tests pass is that opEquals never gets instantiated.Officially, opEquals has to have the signature: struct Foo { bool opEquals(const ref Foo x) const {...} }This is a compiler bug. For structs there should be no official implementation of opEquals, opCmp etc. All the compiler needs to worry about is to syntactically translate a == b to a.opEquals(b) and then let the usual language rules resolve the call.How can opEquals be defined in a way that it works for structs with destructors, and also with rvalues?"auto ref" should be used whenever you want to accept both a value and an rvalue. For structs with destructors see my other post in this thread. Unfortunately, "auto ref" is currently implemented wrongly due to a misunderstanding between Walter and myself. I meant it as a relaxation of binding rules, i.e. "I'm fine with either an rvalue or an lvalue". He thought it's all about generating two template instantiations. In fact auto ref should work when there's no template in sight.Why? It makes perfect sense to qualify the destructor the same way as the originating constructor. It has been a serious limitation of C++ that you couldn't tell during either construction or destruction that a const object was being built/destroyed. One issue with D's const is that people expect to use it most everywhere, much like C++'s const. One thing that I understood early on was that D's const provides much stronger guarantees than C++'s, and as a direct consequence it is more constrained and is usable less often.This scares me. I can see a danger of making structs with destructors practically unusable.But this disallows comparisons with rvalues. eg, Foo bar() { Foo x = 1; return x; } Foo y=1; assert( y == bar() ); // doesn't compile You can get around this by declaring a non-ref opEquals. But this fails if Foo has a destructor. If a struct has a destructor, it cannot be const(this is bug 3606) --- struct S { ~this() {} } void main() { const S z; } --- bug.d(6): Error: destructor bug.S.~this () is not callable using argument types () ------- Likewise, it can't be a const parameter (this is bug 4338). void foo(const S a) {} It works to have it as a const ref parameter. Everything will work if you declare a const ~this(), but that seems a little nonsensical. And you cannot have both const and non-const ~this(). I'm a bit uncertain as to how this is all supposed to work. (1) Should temporaries be allowed to be passed as 'const ref'? (2) If a struct has a destructor, should it be passable as a const parameter? And if so, should the destructor be called?This is a delicate matter that clearly needs a solution. Pass of temporaries by const ref was a huge mistake of C++ that it has paid dearly for and required the introduction of a large complication, the rvalue references feature, to just undo the effects of that mistake. So I don't think we should allow that. Regarding destructors, for every constructed object ever there must be a corresponding destructor call. One issue that has been a matter of debate in C++ has been the fact that any object becomes "deconstified" during destruction. The oddest consequence of that rule is that in C++ you can delete a pointer to a const object: // C++ code class A { ... }; void fun(const A* p) { delete p; /* fine */ } There has been a lot of opposition. const is supposed to limit what you can do with that object, and the fact that you can't invoke certain methods or change members, but you can nuke the entire object, is quite nonintuitive (and leads to a lot of funny real-life comparisons such as "You can go out with my daughter, but no touching. Of course, you can shoot her if you so wish.") In D, the rule must be inferred from D's immutability rules, which pretty much dictate that the destructor must be overloaded for non-const and const (and possibly invariant if the struct needs that).Some unprocessed thoughts are below. Who calls postblit? I think that if you can make a clone of a const object, you should also be able to destroy that clone.Yes. Any object created will also be destroyed, regardless of qualifiers.Seems to me that the natural rule would be, that the creator is responsible for destruction.That rule is roughly C++'s and has an issue that D fixes (in my mind; the implementation is not 100% there yet). Exact issue is discussed below.This would require that, for example, given code like this: const(S) foo(const(S) x) { return x; } inside foo, a non-const S is created, blitted with x, then postblit is called on it, then it is returned as const. On return, the original x is destroyed if it was a temporary. Otherwise, it gets called at the end of its scope. Eg, const w = foo(foo( S(2) )); would be translated into: const w = (foo( S __tmp1 = S(2), const(S) __tmp2 = foo(__tmp1), ~__tmp1, __tmp2), ~__tmp2); Has this sort of idea been explored? Is there something wrong with it?What's wrong with it is it consistently leads to suboptimal code, which has created a hecatomb of problems for C++ (even the very carefully conceived rvalue references feature, for all its size and might, is unable to fix them all). The caller should create the copy and pass the responsibility of destroying to the callee. This is because oftentimes the actual object destroyed is not the same object that was constructed. You see those trailing calls ~__tmp1, ~__tmp2 at the end of your code? They are a tin cat stuck to code's tail. Andrei
Dec 04 2010
bool opEquals(auto ref inout Tuple rhs) inout { foreach (i, T; Types) { if (this[i] != rhs[i]) return false; } return true; } It looks a bit alembicated but let's not forget that Tuple is supposed to be very flexible and to do a lot of things.Const-system is a one big abomination, considering the consequences it is quite hard to say it is something good. As complex as it may look, the above example addresses many problems of this system. I would hate to write equal C++ code. Please lets not add any more keyword/syntax, already forgot we had "auto ref"... -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 04 2010
Keyword cocktails.. 2010/12/4 so <so so.do>:obool opEquals(auto ref inout Tuple rhs) inout { =A0 foreach (i, T; Types) { =A0 =A0 if (this[i] !=3D rhs[i]) return false; =A0 } =A0 return true; } It looks a bit alembicated but let's not forget that Tuple is supposed t=hisbe very flexible and to do a lot of things.Const-system is a one big abomination, considering the consequences it is quite hard to say it is something good. As complex as it may look, the above example addresses many problems of t=system. I would hate to write equal C++ code. Please lets not add any more keyword/syntax, already forgot we had "auto ref"... -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 04 2010
Andrei Alexandrescu wrote:On 12/4/10 9:23 AM, Don wrote:Ouch. The semantics of == are very well defined, and simple. Always, you want read-only access to the two objects, in the fastest possible way. I don't see why the complexity of the object should have any influence on the signature of ==. If there's a method which works correctly and efficiently in every case, why isn't it the only way?Andrei Alexandrescu wrote:I think it should be as follows: bool opEquals(auto ref inout Tuple rhs) inout { foreach (i, T; Types) { if (this[i] != rhs[i]) return false; } return true; } It looks a bit alembicated but let's not forget that Tuple is supposed to be very flexible and to do a lot of things.On 12/4/10 12:42 AM, Don wrote:Fine, but try to implement a generic type which supports ==. For example, Tuple in std.typecons. The only reason that many of the Tuple unit tests pass is that opEquals never gets instantiated.Officially, opEquals has to have the signature: struct Foo { bool opEquals(const ref Foo x) const {...} }This is a compiler bug. For structs there should be no official implementation of opEquals, opCmp etc. All the compiler needs to worry about is to syntactically translate a == b to a.opEquals(b) and then let the usual language rules resolve the call.They need not apply to functions with 'inout' parameters. A parameter which is passed by 'inout' will either be used in the return value, or it will need to be destroyed. It seems clear to me that when you declare an 'inout' parameter, you're assuming responsibility for the lifetime of the object.How can opEquals be defined in a way that it works for structs with destructors, and also with rvalues?"auto ref" should be used whenever you want to accept both a value and an rvalue. For structs with destructors see my other post in this thread. Unfortunately, "auto ref" is currently implemented wrongly due to a misunderstanding between Walter and myself. I meant it as a relaxation of binding rules, i.e. "I'm fine with either an rvalue or an lvalue". He thought it's all about generating two template instantiations. In fact auto ref should work when there's no template in sight.Why? It makes perfect sense to qualify the destructor the same way as the originating constructor. It has been a serious limitation of C++ that you couldn't tell during either construction or destruction that a const object was being built/destroyed. One issue with D's const is that people expect to use it most everywhere, much like C++'s const. One thing that I understood early on was that D's const provides much stronger guarantees than C++'s, and as a direct consequence it is more constrained and is usable less often.This scares me. I can see a danger of making structs with destructors practically unusable.But this disallows comparisons with rvalues. eg, Foo bar() { Foo x = 1; return x; } Foo y=1; assert( y == bar() ); // doesn't compile You can get around this by declaring a non-ref opEquals. But this fails if Foo has a destructor. If a struct has a destructor, it cannot be const(this is bug 3606) --- struct S { ~this() {} } void main() { const S z; } --- bug.d(6): Error: destructor bug.S.~this () is not callable using argument types () ------- Likewise, it can't be a const parameter (this is bug 4338). void foo(const S a) {} It works to have it as a const ref parameter. Everything will work if you declare a const ~this(), but that seems a little nonsensical. And you cannot have both const and non-const ~this(). I'm a bit uncertain as to how this is all supposed to work. (1) Should temporaries be allowed to be passed as 'const ref'? (2) If a struct has a destructor, should it be passable as a const parameter? And if so, should the destructor be called?This is a delicate matter that clearly needs a solution. Pass of temporaries by const ref was a huge mistake of C++ that it has paid dearly for and required the introduction of a large complication, the rvalue references feature, to just undo the effects of that mistake. So I don't think we should allow that. Regarding destructors, for every constructed object ever there must be a corresponding destructor call. One issue that has been a matter of debate in C++ has been the fact that any object becomes "deconstified" during destruction. The oddest consequence of that rule is that in C++ you can delete a pointer to a const object: // C++ code class A { ... }; void fun(const A* p) { delete p; /* fine */ } There has been a lot of opposition. const is supposed to limit what you can do with that object, and the fact that you can't invoke certain methods or change members, but you can nuke the entire object, is quite nonintuitive (and leads to a lot of funny real-life comparisons such as "You can go out with my daughter, but no touching. Of course, you can shoot her if you so wish.") In D, the rule must be inferred from D's immutability rules, which pretty much dictate that the destructor must be overloaded for non-const and const (and possibly invariant if the struct needs that).Some unprocessed thoughts are below. Who calls postblit? I think that if you can make a clone of a const object, you should also be able to destroy that clone.Yes. Any object created will also be destroyed, regardless of qualifiers.Seems to me that the natural rule would be, that the creator is responsible for destruction.That rule is roughly C++'s and has an issue that D fixes (in my mind; the implementation is not 100% there yet). Exact issue is discussed below.This would require that, for example, given code like this: const(S) foo(const(S) x) { return x; } inside foo, a non-const S is created, blitted with x, then postblit is called on it, then it is returned as const. On return, the original x is destroyed if it was a temporary. Otherwise, it gets called at the end of its scope. Eg, const w = foo(foo( S(2) )); would be translated into: const w = (foo( S __tmp1 = S(2), const(S) __tmp2 = foo(__tmp1), ~__tmp1, __tmp2), ~__tmp2); Has this sort of idea been explored? Is there something wrong with it?What's wrong with it is it consistently leads to suboptimal code, which has created a hecatomb of problems for C++ (even the very carefully conceived rvalue references feature, for all its size and might, is unable to fix them all). The caller should create the copy and pass the responsibility of destroying to the callee. This is because oftentimes the actual object destroyed is not the same object that was constructed. You see those trailing calls ~__tmp1, ~__tmp2 at the end of your code? They are a tin cat stuck to code's tail.
Dec 04 2010
On 12/4/10 2:39 PM, Don wrote:Andrei Alexandrescu wrote:It's not the complexity of the object as much as "don't pay for const if you don't use it". If Tuple's opEquals is implemented as above, it works with code that doesn't use const at all. Tack a const on to it, everybody must define opEquals with const. I would agree if there were a wide agreement out there that opEquals must have a const signature. In that case, the required signature should be: bool opEquals(auto ref const T) const; "auto ref", again, is NOT two templates into one, it's argument binding relaxation. [snip]On 12/4/10 9:23 AM, Don wrote:Ouch. The semantics of == are very well defined, and simple. Always, you want read-only access to the two objects, in the fastest possible way. I don't see why the complexity of the object should have any influence on the signature of ==. If there's a method which works correctly and efficiently in every case, why isn't it the only way?Andrei Alexandrescu wrote:I think it should be as follows: bool opEquals(auto ref inout Tuple rhs) inout { foreach (i, T; Types) { if (this[i] != rhs[i]) return false; } return true; } It looks a bit alembicated but let's not forget that Tuple is supposed to be very flexible and to do a lot of things.On 12/4/10 12:42 AM, Don wrote:Fine, but try to implement a generic type which supports ==. For example, Tuple in std.typecons. The only reason that many of the Tuple unit tests pass is that opEquals never gets instantiated.Officially, opEquals has to have the signature: struct Foo { bool opEquals(const ref Foo x) const {...} }This is a compiler bug. For structs there should be no official implementation of opEquals, opCmp etc. All the compiler needs to worry about is to syntactically translate a == b to a.opEquals(b) and then let the usual language rules resolve the call.I'm not sure I understand, sorry. To recap, "inout" used to mean "ref" but not anymore. It just means "this stands for either const, immutable, or nothing". I'm not sure how that affects caller's responsibility. And to clarify: due to D's rule that all structs are moveable, the object against which the destructor will be called may be different than the one against which the constructor was called. Therefore, the rule that the creator code is always responsible for destruction is not applicable. AndreiThey need not apply to functions with 'inout' parameters. A parameter which is passed by 'inout' will either be used in the return value, or it will need to be destroyed. It seems clear to me that when you declare an 'inout' parameter, you're assuming responsibility for the lifetime of the object.Has this sort of idea been explored? Is there something wrong with it?What's wrong with it is it consistently leads to suboptimal code, which has created a hecatomb of problems for C++ (even the very carefully conceived rvalue references feature, for all its size and might, is unable to fix them all). The caller should create the copy and pass the responsibility of destroying to the callee. This is because oftentimes the actual object destroyed is not the same object that was constructed. You see those trailing calls ~__tmp1, ~__tmp2 at the end of your code? They are a tin cat stuck to code's tail.
Dec 04 2010
On Sat, 04 Dec 2010 15:58:43 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:On 12/4/10 2:39 PM, Don wrote:You have not addressed that problem -- tack an inout on it, everybody must define opEquals with inout.Andrei Alexandrescu wrote:It's not the complexity of the object as much as "don't pay for const if you don't use it". If Tuple's opEquals is implemented as above, it works with code that doesn't use const at all. Tack a const on to it, everybody must define opEquals with const.On 12/4/10 9:23 AM, Don wrote:Ouch. The semantics of == are very well defined, and simple. Always, you want read-only access to the two objects, in the fastest possible way. I don't see why the complexity of the object should have any influence on the signature of ==. If there's a method which works correctly and efficiently in every case, why isn't it the only way?Andrei Alexandrescu wrote:I think it should be as follows: bool opEquals(auto ref inout Tuple rhs) inout { foreach (i, T; Types) { if (this[i] != rhs[i]) return false; } return true; } It looks a bit alembicated but let's not forget that Tuple is supposed to be very flexible and to do a lot of things.On 12/4/10 12:42 AM, Don wrote:Fine, but try to implement a generic type which supports ==. For example, Tuple in std.typecons. The only reason that many of the Tuple unit tests pass is that opEquals never gets instantiated.Officially, opEquals has to have the signature: struct Foo { bool opEquals(const ref Foo x) const {...} }This is a compiler bug. For structs there should be no official implementation of opEquals, opCmp etc. All the compiler needs to worry about is to syntactically translate a == b to a.opEquals(b) and then let the usual language rules resolve the call.[snip]It does not stand for const, immutable, or nothing exactly. It binds the constancy of the output with the constancy of the inputs in an enforceable way. It imposes a temporary const on everything, and then returns things back to the way they were, even though you are returning a portion of a parameter. -SteveI'm not sure I understand, sorry. To recap, "inout" used to mean "ref" but not anymore. It just means "this stands for either const, immutable, or nothing". I'm not sure how that affects caller's responsibility.They need not apply to functions with 'inout' parameters. A parameter which is passed by 'inout' will either be used in the return value, or it will need to be destroyed. It seems clear to me that when you declare an 'inout' parameter, you're assuming responsibility for the lifetime of the object.Has this sort of idea been explored? Is there something wrong with it?What's wrong with it is it consistently leads to suboptimal code, which has created a hecatomb of problems for C++ (even the very carefully conceived rvalue references feature, for all its size and might, is unable to fix them all). The caller should create the copy and pass the responsibility of destroying to the callee. This is because oftentimes the actual object destroyed is not the same object that was constructed. You see those trailing calls ~__tmp1, ~__tmp2 at the end of your code? They are a tin cat stuck to code's tail.
Dec 04 2010
On 12/4/10 22:42 CST, Steven Schveighoffer wrote:On Sat, 04 Dec 2010 15:58:43 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:I don't think so. On the contrary, declaring with inout is the most adaptive.On 12/4/10 2:39 PM, Don wrote:You have not addressed that problem -- tack an inout on it, everybody must define opEquals with inout.Andrei Alexandrescu wrote:It's not the complexity of the object as much as "don't pay for const if you don't use it". If Tuple's opEquals is implemented as above, it works with code that doesn't use const at all. Tack a const on to it, everybody must define opEquals with const.On 12/4/10 9:23 AM, Don wrote:Ouch. The semantics of == are very well defined, and simple. Always, you want read-only access to the two objects, in the fastest possible way. I don't see why the complexity of the object should have any influence on the signature of ==. If there's a method which works correctly and efficiently in every case, why isn't it the only way?Andrei Alexandrescu wrote:I think it should be as follows: bool opEquals(auto ref inout Tuple rhs) inout { foreach (i, T; Types) { if (this[i] != rhs[i]) return false; } return true; } It looks a bit alembicated but let's not forget that Tuple is supposed to be very flexible and to do a lot of things.On 12/4/10 12:42 AM, Don wrote:Fine, but try to implement a generic type which supports ==. For example, Tuple in std.typecons. The only reason that many of the Tuple unit tests pass is that opEquals never gets instantiated.Officially, opEquals has to have the signature: struct Foo { bool opEquals(const ref Foo x) const {...} }This is a compiler bug. For structs there should be no official implementation of opEquals, opCmp etc. All the compiler needs to worry about is to syntactically translate a == b to a.opEquals(b) and then let the usual language rules resolve the call.Yah, still not getting the original point there. Andrei[snip]It does not stand for const, immutable, or nothing exactly. It binds the constancy of the output with the constancy of the inputs in an enforceable way. It imposes a temporary const on everything, and then returns things back to the way they were, even though you are returning a portion of a parameter.I'm not sure I understand, sorry. To recap, "inout" used to mean "ref" but not anymore. It just means "this stands for either const, immutable, or nothing". I'm not sure how that affects caller's responsibility.They need not apply to functions with 'inout' parameters. A parameter which is passed by 'inout' will either be used in the return value, or it will need to be destroyed. It seems clear to me that when you declare an 'inout' parameter, you're assuming responsibility for the lifetime of the object.Has this sort of idea been explored? Is there something wrong with it?What's wrong with it is it consistently leads to suboptimal code, which has created a hecatomb of problems for C++ (even the very carefully conceived rvalue references feature, for all its size and might, is unable to fix them all). The caller should create the copy and pass the responsibility of destroying to the callee. This is because oftentimes the actual object destroyed is not the same object that was constructed. You see those trailing calls ~__tmp1, ~__tmp2 at the end of your code? They are a tin cat stuck to code's tail.
Dec 04 2010
On Sun, 05 Dec 2010 00:42:20 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Yah, still not getting the original point there.I responded better in another part of this thread. Basically, inout doesn't mean what you think it means. -Steve
Dec 04 2010
On 12/5/10 12:06 AM, Steven Schveighoffer wrote:On Sun, 05 Dec 2010 00:42:20 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Oh, I see. I thought its behavior can be equivalent to producing three functions, each replacing inout with (a) nothing, (b) const, (c) immutable. This approximation works only if all methods actually end up having the same code. AndreiYah, still not getting the original point there.I responded better in another part of this thread. Basically, inout doesn't mean what you think it means.
Dec 05 2010
On Sat, 04 Dec 2010 11:00:58 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:On 12/4/10 9:23 AM, Don wrote:No no no, inout does not belong here. Use const. inout is only used if you are returning a portion of the arguments. That should be a hard rule by the compiler (error). Fixed: bool opEquals(auto ref const(Tuple) rhs) constAndrei Alexandrescu wrote:I think it should be as follows: bool opEquals(auto ref inout Tuple rhs) inout { foreach (i, T; Types) { if (this[i] != rhs[i]) return false; } return true; }On 12/4/10 12:42 AM, Don wrote:Fine, but try to implement a generic type which supports ==. For example, Tuple in std.typecons. The only reason that many of the Tuple unit tests pass is that opEquals never gets instantiated.Officially, opEquals has to have the signature: struct Foo { bool opEquals(const ref Foo x) const {...} }This is a compiler bug. For structs there should be no official implementation of opEquals, opCmp etc. All the compiler needs to worry about is to syntactically translate a == b to a.opEquals(b) and then let the usual language rules resolve the call.But it must instantiate two functions, no? How does one call the same function with by ref or by value? And when inside the function, the code generation for a ref storage class is going to be drastically different, right? BTW, I agree with the point, it should not require templates. But I think it does result in two functions. Actually, thinking about it more, how does this work? T foo(); T bar(); if(foo() == bar()) both are temporaries, but opEquals passes 'this' by reference. So there we have a case where a reference of a temporary is passed. Does this make sense? Indeed, I have used this 'trick' to get around the discussed limitations while writing dcollections. Just always compare using 'rvalue == lvalue'. -SteveHow can opEquals be defined in a way that it works for structs with destructors, and also with rvalues?"auto ref" should be used whenever you want to accept both a value and an rvalue. For structs with destructors see my other post in this thread. Unfortunately, "auto ref" is currently implemented wrongly due to a misunderstanding between Walter and myself. I meant it as a relaxation of binding rules, i.e. "I'm fine with either an rvalue or an lvalue". He thought it's all about generating two template instantiations. In fact auto ref should work when there's no template in sight.
Dec 04 2010
On 12/4/10 22:36 CST, Steven Schveighoffer wrote:On Sat, 04 Dec 2010 11:00:58 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Then you handle the angry crowds for me please.On 12/4/10 9:23 AM, Don wrote:No no no, inout does not belong here. Use const. inout is only used if you are returning a portion of the arguments. That should be a hard rule by the compiler (error). Fixed: bool opEquals(auto ref const(Tuple) rhs) constAndrei Alexandrescu wrote:I think it should be as follows: bool opEquals(auto ref inout Tuple rhs) inout { foreach (i, T; Types) { if (this[i] != rhs[i]) return false; } return true; }On 12/4/10 12:42 AM, Don wrote:Fine, but try to implement a generic type which supports ==. For example, Tuple in std.typecons. The only reason that many of the Tuple unit tests pass is that opEquals never gets instantiated.Officially, opEquals has to have the signature: struct Foo { bool opEquals(const ref Foo x) const {...} }This is a compiler bug. For structs there should be no official implementation of opEquals, opCmp etc. All the compiler needs to worry about is to syntactically translate a == b to a.opEquals(b) and then let the usual language rules resolve the call.No.But it must instantiate two functions, no?How can opEquals be defined in a way that it works for structs with destructors, and also with rvalues?"auto ref" should be used whenever you want to accept both a value and an rvalue. For structs with destructors see my other post in this thread. Unfortunately, "auto ref" is currently implemented wrongly due to a misunderstanding between Walter and myself. I meant it as a relaxation of binding rules, i.e. "I'm fine with either an rvalue or an lvalue". He thought it's all about generating two template instantiations. In fact auto ref should work when there's no template in sight.How does one call the same function with by ref or by value?By always using ref.And when inside the function, the code generation for a ref storage class is going to be drastically different, right?No.BTW, I agree with the point, it should not require templates. But I think it does result in two functions.No.Actually, thinking about it more, how does this work? T foo(); T bar(); if(foo() == bar()) both are temporaries, but opEquals passes 'this' by reference. So there we have a case where a reference of a temporary is passed. Does this make sense?Yah. Method calls are already passed by reference (I'd prefer not but that's just me). Andrei
Dec 04 2010
On 12/4/10 23:40 CST, Andrei Alexandrescu wrote:Yah. Method calls are already passed by reference (I'd prefer not but that's just me).Sorry, I meant: method calls are already allowed for rvalues. Sleepy... Andrei
Dec 04 2010
On Sun, 05 Dec 2010 00:40:26 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:On 12/4/10 22:36 CST, Steven Schveighoffer wrote:Huh? I don't think you understand what I mean. inout only implicitly converts to const. Example: struct S { bool opEquals(S rhs){return false;} } struct T { S s; bool opEquals(auto ref inout T rhs) inout { return s == rhs.s; // error, cannot call S.opEquals(S rhs) with parameters (inout S) inout } } You gain nothing from making opEquals of Tuple inout vs. const. IMO all opEquals should be const functions, and the parameter should be const if it is marked as ref, or it contains references.On Sat, 04 Dec 2010 11:00:58 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Then you handle the angry crowds for me please.On 12/4/10 9:23 AM, Don wrote:No no no, inout does not belong here. Use const. inout is only used if you are returning a portion of the arguments. That should be a hard rule by the compiler (error). Fixed: bool opEquals(auto ref const(Tuple) rhs) constAndrei Alexandrescu wrote:I think it should be as follows: bool opEquals(auto ref inout Tuple rhs) inout { foreach (i, T; Types) { if (this[i] != rhs[i]) return false; } return true; }On 12/4/10 12:42 AM, Don wrote:Fine, but try to implement a generic type which supports ==. For example, Tuple in std.typecons. The only reason that many of the Tuple unit tests pass is that opEquals never gets instantiated.Officially, opEquals has to have the signature: struct Foo { bool opEquals(const ref Foo x) const {...} }This is a compiler bug. For structs there should be no official implementation of opEquals, opCmp etc. All the compiler needs to worry about is to syntactically translate a == b to a.opEquals(b) and then let the usual language rules resolve the call.I'm totally confused. I thought the point of auto ref was to pass by value if it's an rvalue (since the data is already on the stack). If this is not the case, then why not just make ref work that way? Why wouldn't I mark all my functions as auto ref to avoid being pestered by the compiler? -SteveNo.But it must instantiate two functions, no?How can opEquals be defined in a way that it works for structs with destructors, and also with rvalues?"auto ref" should be used whenever you want to accept both a value and an rvalue. For structs with destructors see my other post in this thread. Unfortunately, "auto ref" is currently implemented wrongly due to a misunderstanding between Walter and myself. I meant it as a relaxation of binding rules, i.e. "I'm fine with either an rvalue or an lvalue". He thought it's all about generating two template instantiations. In fact auto ref should work when there's no template in sight.How does one call the same function with by ref or by value?By always using ref.
Dec 04 2010
Huh? I don't think you understand what I mean. inout only implicitly converts to const. Example: struct S { bool opEquals(S rhs){return false;} } struct T { S s; bool opEquals(auto ref inout T rhs) inout { return s == rhs.s; // error, cannot call S.opEquals(S rhs) with parameters (inout S) inout } } You gain nothing from making opEquals of Tuple inout vs. const. IMO all opEquals should be const functions, and the parameter should be const if it is marked as ref, or it contains references.You are right. Reading it again, using inout is wrong here, both as parameter and function qualifier. Only thing missing here is auto ref being broken(?). Using inout in parameter list already giving error here as it should. -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 05 2010
On 12/5/10 12:04 AM, Steven Schveighoffer wrote:I'm totally confused. I thought the point of auto ref was to pass by value if it's an rvalue (since the data is already on the stack). If this is not the case, then why not just make ref work that way? Why wouldn't I mark all my functions as auto ref to avoid being pestered by the compiler?Because you sometimes do care about dealing with a true lvalue. Consider: void bump(ref int x) { ++x; } Then: unsigned int y; bump(y); // you don't want this to go through short z; bump(z); // you don't want this to go through int w; bump(w * 2); // you don't want this to go through Andrei
Dec 05 2010
On Sun, 05 Dec 2010 09:18:13 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:On 12/5/10 12:04 AM, Steven Schveighoffer wrote:Right. OK, so now I understand what you are saying, but now I don't understand why const ref is such a mistake. Before you explained it was because when you pass an rvalue by ref, it's much more expensive, so auto ref passes by ref if it's an lvalue and by value if it's an rvalue. At least that's what I understood. With const ref, you get the same behavior, plus you are guaranteed that the code isn't going to do something stupid (like modify a value that will be thrown away at the end). -SteveI'm totally confused. I thought the point of auto ref was to pass by value if it's an rvalue (since the data is already on the stack). If this is not the case, then why not just make ref work that way? Why wouldn't I mark all my functions as auto ref to avoid being pestered by the compiler?Because you sometimes do care about dealing with a true lvalue. Consider: void bump(ref int x) { ++x; } Then: unsigned int y; bump(y); // you don't want this to go through short z; bump(z); // you don't want this to go through int w; bump(w * 2); // you don't want this to go through
Dec 06 2010
On Mon, 06 Dec 2010 08:34:20 -0500, Steven Schveighoffer <schveiguy yahoo.com> wrote:On Sun, 05 Dec 2010 09:18:13 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Not sure if this got lost in the noise, I'm still puzzled about this... -SteveOn 12/5/10 12:04 AM, Steven Schveighoffer wrote:Right. OK, so now I understand what you are saying, but now I don't understand why const ref is such a mistake. Before you explained it was because when you pass an rvalue by ref, it's much more expensive, so auto ref passes by ref if it's an lvalue and by value if it's an rvalue. At least that's what I understood. With const ref, you get the same behavior, plus you are guaranteed that the code isn't going to do something stupid (like modify a value that will be thrown away at the end).I'm totally confused. I thought the point of auto ref was to pass by value if it's an rvalue (since the data is already on the stack). If this is not the case, then why not just make ref work that way? Why wouldn't I mark all my functions as auto ref to avoid being pestered by the compiler?Because you sometimes do care about dealing with a true lvalue. Consider: void bump(ref int x) { ++x; } Then: unsigned int y; bump(y); // you don't want this to go through short z; bump(z); // you don't want this to go through int w; bump(w * 2); // you don't want this to go through
Dec 10 2010
On 12/10/10 12:46 PM, Steven Schveighoffer wrote:On Mon, 06 Dec 2010 08:34:20 -0500, Steven Schveighoffer <schveiguy yahoo.com> wrote:Sorry, indeed I haven't seen it. The problem with binding rvalues to const ref is that once that is in place you have no way to distinguish an rvalue from a const ref on the callee site. If you do want to distinguish, you must rely on complicated conversion priorities. For example, consider: void foo(ref const Widget); void foo(Widget); You'd sometimes want to do that because you want to exploit an rvalue by e.g. moving its state instead of copying it. However, if rvalues become convertible to ref const, then they are motivated to go either way. A rule could be put in place that gives priority to the second declaration. However, things quickly get complicated in the presence of other applicable rules, multiple parameters etc. Essentially it was impossible for C++ to go this way and that's how rvalue references were born. For D I want to avoid all that aggravation and have a simple rule: rvalues don't bind to references to const. If you don't care, use auto ref. This is a simple rule that works promisingly well in various forwarding scenarios. AndreiOn Sun, 05 Dec 2010 09:18:13 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Not sure if this got lost in the noise, I'm still puzzled about this...On 12/5/10 12:04 AM, Steven Schveighoffer wrote:Right. OK, so now I understand what you are saying, but now I don't understand why const ref is such a mistake. Before you explained it was because when you pass an rvalue by ref, it's much more expensive, so auto ref passes by ref if it's an lvalue and by value if it's an rvalue. At least that's what I understood. With const ref, you get the same behavior, plus you are guaranteed that the code isn't going to do something stupid (like modify a value that will be thrown away at the end).I'm totally confused. I thought the point of auto ref was to pass by value if it's an rvalue (since the data is already on the stack). If this is not the case, then why not just make ref work that way? Why wouldn't I mark all my functions as auto ref to avoid being pestered by the compiler?Because you sometimes do care about dealing with a true lvalue. Consider: void bump(ref int x) { ++x; } Then: unsigned int y; bump(y); // you don't want this to go through short z; bump(z); // you don't want this to go through int w; bump(w * 2); // you don't want this to go through
Dec 10 2010
On Fri, 10 Dec 2010 15:58:17 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:On 12/10/10 12:46 PM, Steven Schveighoffer wrote:OK, now I get it, thanks for explaining it again :) So essentially you are overriding the "no ref rvalues" rule, this is a good thing, because many times the compiler is too conservative in that decision. To summarize for those looking for the C++ behavior, the equivalent would be: void foo(auto ref const Widget) right? Well, at least when it's implemented properly :) BTW, is there a bugzilla entry on this? -SteveOn Mon, 06 Dec 2010 08:34:20 -0500, Steven Schveighoffer <schveiguy yahoo.com> wrote:Sorry, indeed I haven't seen it. The problem with binding rvalues to const ref is that once that is in place you have no way to distinguish an rvalue from a const ref on the callee site. If you do want to distinguish, you must rely on complicated conversion priorities. For example, consider: void foo(ref const Widget); void foo(Widget); You'd sometimes want to do that because you want to exploit an rvalue by e.g. moving its state instead of copying it. However, if rvalues become convertible to ref const, then they are motivated to go either way. A rule could be put in place that gives priority to the second declaration. However, things quickly get complicated in the presence of other applicable rules, multiple parameters etc. Essentially it was impossible for C++ to go this way and that's how rvalue references were born. For D I want to avoid all that aggravation and have a simple rule: rvalues don't bind to references to const. If you don't care, use auto ref. This is a simple rule that works promisingly well in various forwarding scenarios.On Sun, 05 Dec 2010 09:18:13 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Not sure if this got lost in the noise, I'm still puzzled about this...On 12/5/10 12:04 AM, Steven Schveighoffer wrote:Right. OK, so now I understand what you are saying, but now I don't understand why const ref is such a mistake. Before you explained it was because when you pass an rvalue by ref, it's much more expensive, so auto ref passes by ref if it's an lvalue and by value if it's an rvalue. At least that's what I understood. With const ref, you get the same behavior, plus you are guaranteed that the code isn't going to do something stupid (like modify a value that will be thrown away at the end).I'm totally confused. I thought the point of auto ref was to pass by value if it's an rvalue (since the data is already on the stack). If this is not the case, then why not just make ref work that way? Why wouldn't I mark all my functions as auto ref to avoid being pestered by the compiler?Because you sometimes do care about dealing with a true lvalue. Consider: void bump(ref int x) { ++x; } Then: unsigned int y; bump(y); // you don't want this to go through short z; bump(z); // you don't want this to go through int w; bump(w * 2); // you don't want this to go through
Dec 10 2010
On 12/10/10 1:10 PM, Steven Schveighoffer wrote:On Fri, 10 Dec 2010 15:58:17 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:That is correct. There are a couple of related bug reports (http://d.puremagic.com/issues/show_bug.cgi?id=4668, http://d.puremagic.com/issues/show_bug.cgi?id=4258) so I'm not worried about the problem being forgotten. We all need to think about this a bit more because it's related to another issue that I'm still losing sleep over: should we promote cheap copy construction throughout D or not? AndreiOn 12/10/10 12:46 PM, Steven Schveighoffer wrote:OK, now I get it, thanks for explaining it again :) So essentially you are overriding the "no ref rvalues" rule, this is a good thing, because many times the compiler is too conservative in that decision. To summarize for those looking for the C++ behavior, the equivalent would be: void foo(auto ref const Widget) right? Well, at least when it's implemented properly :) BTW, is there a bugzilla entry on this?On Mon, 06 Dec 2010 08:34:20 -0500, Steven Schveighoffer <schveiguy yahoo.com> wrote:Sorry, indeed I haven't seen it. The problem with binding rvalues to const ref is that once that is in place you have no way to distinguish an rvalue from a const ref on the callee site. If you do want to distinguish, you must rely on complicated conversion priorities. For example, consider: void foo(ref const Widget); void foo(Widget); You'd sometimes want to do that because you want to exploit an rvalue by e.g. moving its state instead of copying it. However, if rvalues become convertible to ref const, then they are motivated to go either way. A rule could be put in place that gives priority to the second declaration. However, things quickly get complicated in the presence of other applicable rules, multiple parameters etc. Essentially it was impossible for C++ to go this way and that's how rvalue references were born. For D I want to avoid all that aggravation and have a simple rule: rvalues don't bind to references to const. If you don't care, use auto ref. This is a simple rule that works promisingly well in various forwarding scenarios.On Sun, 05 Dec 2010 09:18:13 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Not sure if this got lost in the noise, I'm still puzzled about this...On 12/5/10 12:04 AM, Steven Schveighoffer wrote:Right. OK, so now I understand what you are saying, but now I don't understand why const ref is such a mistake. Before you explained it was because when you pass an rvalue by ref, it's much more expensive, so auto ref passes by ref if it's an lvalue and by value if it's an rvalue. At least that's what I understood. With const ref, you get the same behavior, plus you are guaranteed that the code isn't going to do something stupid (like modify a value that will be thrown away at the end).I'm totally confused. I thought the point of auto ref was to pass by value if it's an rvalue (since the data is already on the stack). If this is not the case, then why not just make ref work that way? Why wouldn't I mark all my functions as auto ref to avoid being pestered by the compiler?Because you sometimes do care about dealing with a true lvalue. Consider: void bump(ref int x) { ++x; } Then: unsigned int y; bump(y); // you don't want this to go through short z; bump(z); // you don't want this to go through int w; bump(w * 2); // you don't want this to go through
Dec 10 2010
On 10/12/2010 21:17, Andrei Alexandrescu wrote:We all need to think about this a bit more because it's related to another issue that I'm still losing sleep over: should we promote cheap copy construction throughout D or not?I was reminded of another comment that could be said in favor of that: If you look back at your own article and thoughts about ranges and iteration, you made the case for the benefits of the iteration primitives having complexity guarantees (just as is the case with STL). It seems to me that the very same reasoning could be applied to these fundamental type primitives, like the copy constructor at least. If the copy constructor guarantees constant complexity, then other algorithms and operations can be built on top of that, and also provide useful complexity guarantees. Like the sort example you mentioned. (Hum, and if we go this way, it will probably be best not to call it "copy constructor" then) -- Bruno Medeiros - Software Engineer
Dec 21 2010
Steven Schveighoffer wrote:To summarize for those looking for the C++ behavior, the equivalent would be: void foo(auto ref const Widget)That use of 'auto' is an abomination.
Dec 10 2010
Don Wrote:Steven Schveighoffer wrote:I agree with don. IMHO, this is incredibly silly given Andrei's use case, since D can have instead: void foo(const Widget); and have an optimization inside the compiler for value types to pass by ref. by specifying "const ref" you explicitly require that only a ref to an l-value be provided, whereas without the "ref" an r-value is also allowed a-la c++. much KISSer.To summarize for those looking for the C++ behavior, the equivalent would be: void foo(auto ref const Widget)That use of 'auto' is an abomination.
Dec 10 2010
On 12/10/10 4:10 PM, foobar wrote:Don Wrote:Everyone - please stop suggesting that. It causes severe undue aliasing issues. AndreiSteven Schveighoffer wrote:I agree with don. IMHO, this is incredibly silly given Andrei's use case, since D can have instead: void foo(const Widget); and have an optimization inside the compiler for value types to pass by ref.To summarize for those looking for the C++ behavior, the equivalent would be: void foo(auto ref const Widget)That use of 'auto' is an abomination.
Dec 10 2010
On 2010-12-10 19:32:30 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> said:On 12/10/10 4:10 PM, foobar wrote:If I understand you well Andrei, what you want "auto ref" to be the same thing as "ref" except that it would also accept rvalues. I think the reason you want this is because for some types it is more efficient to pass them as "ref" than by value, so you want to pass them as "ref" for efficiency and and not necessarily for its semantics. And from there goes the need for a "ref" that also accepts rvalues. I think this is a bad usage of "ref". Efficient should be the way arguments are passed by default, and modifiers should be used to alter semantics and not required for efficiency (in most situations). Is there a way to pass arguments more efficiently without introducing a bazillion options the programmer then has to choose from? Perhaps we're just trying to address the problem from the wrong end. Instead of having to say for each function parameter how you want it to be passed, what if the type itself knew how it should be passed as a parameter? passbyref struct ArrayOf50 { float[50] content; } string test1(ArrayOf50 a); // accepts rvalues string test2(ref ArrayOf50 a); // rejects rvalues void main() { test1(ArrayOf50()); test2(ArrayOf50()); // error, first argument requires a reference } Now, obviously we've given different semantics to the type itself, but those semantics are going to be consistent and predictable everywhere. But mostly, you don't have to remember how to pass this struct every time now. That's a really big gain. Also note how you could use this feature to design containers which don't need to be reference counted but which are still passed efficiently across function calls. -- Michel Fortin michel.fortin michelf.com http://michelf.com/Don Wrote:Everyone - please stop suggesting that. It causes severe undue aliasing issues.Steven Schveighoffer wrote:I agree with don. IMHO, this is incredibly silly given Andrei's use case, since D can have instead: void foo(const Widget); and have an optimization inside the compiler for value types to pass by ref.To summarize for those looking for the C++ behavior, the equivalent would be: void foo(auto ref const Widget)That use of 'auto' is an abomination.
Dec 11 2010
On Sat, 11 Dec 2010 09:06:33 -0500 Michel Fortin <michel.fortin michelf.com> wrote:On 2010-12-10 19:32:30 -0500, Andrei Alexandrescu=20 <SeeWebsiteForEmail erdani.org> said: =20y ref.On 12/10/10 4:10 PM, foobar wrote:Don Wrote: =20Steven Schveighoffer wrote:=20 I agree with don. IMHO, this is incredibly silly given Andrei's use case, since D can=20 have instead: void foo(const Widget); and have an optimization inside the compiler for value types to pass b=To summarize for those looking for the C++ behavior, the equivalent would be: =20 void foo(auto ref const Widget)=20 That use of 'auto' is an abomination.issues.=20 Everyone - please stop suggesting that. It causes severe undue aliasing==20 If I understand you well Andrei, what you want "auto ref" to be the=20 same thing as "ref" except that it would also accept rvalues. I think=20 the reason you want this is because for some types it is more efficient=20 to pass them as "ref" than by value, so you want to pass them as "ref"=20 for efficiency and and not necessarily for its semantics. And from=20 there goes the need for a "ref" that also accepts rvalues. =20 I think this is a bad usage of "ref". Efficient should be the way=20 arguments are passed by default, and modifiers should be used to alter=20 semantics and not required for efficiency (in most situations). Is=20 there a way to pass arguments more efficiently without introducing a=20 bazillion options the programmer then has to choose from? =20 Perhaps we're just trying to address the problem from the wrong end.=20 Instead of having to say for each function parameter how you want it to=20 be passed, what if the type itself knew how it should be passed as a=20 parameter? =20 passbyref struct ArrayOf50 { float[50] content; } =20 string test1(ArrayOf50 a); // accepts rvalues string test2(ref ArrayOf50 a); // rejects rvalues =20 void main() { test1(ArrayOf50()); test2(ArrayOf50()); // error, first argument requires a reference } =20 Now, obviously we've given different semantics to the type itself, but=20 those semantics are going to be consistent and predictable everywhere.=20 But mostly, you don't have to remember how to pass this struct every=20 time now. That's a really big gain. =20 Also note how you could use this feature to design containers which=20 don't need to be reference counted but which are still passed=20 efficiently across function calls.=20 If parameters are 'in' or 'const' by default, then whether they are passed = by value or by ref has no consequence, I guess. The compiler can then safel= y choose the most efficent more --what it can do as it knows sizeof-- gross= ly structs by ref, the rest by value. Is this reasoning correct? Denis -- -- -- -- -- -- -- vit esse estrany =E2=98=A3 spir.wikidot.com
Dec 11 2010
On 2010-12-11 11:48:36 -0500, spir <denis.spir gmail.com> said:If parameters are 'in' or 'const' by default, then whether they are passed by value or by ref has no consequence, I guess. The compiler can then safel y choose the most efficent more --what it can do as it knows sizeof-- gross ly structs by ref, the rest by value. Is this reasoning correct?No it can't because of aliasing (the object might be referenced by something else). Look at this simple example: struct A { int value; } bool test(const A a1, ref A a2) { ++a2.value; return a1.value == a2.value; } void main() { A a; a.value = 8; bool result = test(a, a); assert(result == false); assert(a.value == 9); } If "a1" is passed by ref under the hood, it can only be done whenever you know for sure there is no aliasing, or if the data is "immutable", otherwise you'll have side effects. So while it could be done in some circumstances (strongly pure functions and immutable parameters), there's still an important need for a more general solution. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Dec 11 2010
If parameters are 'in' or 'const' by default, then whether they are passed by value or by ref has no consequence, I guess. The compiler can then safely choose the most efficent more --what it can do as it knows sizeof-- grossly structs by ref, the rest by value. Is this reasoning correct?This kind of design decisions, leaving this responsibility to compiler is very wrong. Also, you are introducing a lock in here, no way to do the opposite. "T& a" "ref T a" are very good tools. You can't always assume compiler always knows the best. "If parameters are in or const" what about if not? Another inconsistency and a quite a bad one at it. -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 11 2010
Andrei Alexandrescu wrote:On 12/10/10 4:10 PM, foobar wrote:I don't understand this. For sure, const Widget foo(const Widget x) { return x; } is inefficient. But I don't see how problems can ever arise with a function which returns a built-in type (eg, opEquals() ). It seems to me, that the issue relates to deterministic destruction. As I see it, there can be two forms of const parameters: * caller manages lifetime. Caller must call destructor. It must duplicate anything it wants to return. * callee manages lifetime. Callee must destroy the variable, or return it. Interestingly, any parameter marked as 'inout' is the second form. Seems pretty clear to me that opEquals needs the first form. And I think it's a pretty common case: I'm only going to look at this variable, I'm not going to take ownership of it or modify it any way. We have ref const in inout scope 'auto ref' (which does not mean auto + ref). And yet, even with this zoo of modifiers, the best syntax we have for that simple situation is 'auto ref const' ??? We've got to do better than that.Don Wrote:Everyone - please stop suggesting that. It causes severe undue aliasing issues. AndreiSteven Schveighoffer wrote:I agree with don. IMHO, this is incredibly silly given Andrei's use case, since D can have instead: void foo(const Widget); and have an optimization inside the compiler for value types to pass by ref.To summarize for those looking for the C++ behavior, the equivalent would be: void foo(auto ref const Widget)That use of 'auto' is an abomination.
Dec 13 2010
On 12/13/10 9:28 AM, Don wrote:Andrei Alexandrescu wrote:I agree we should ideally do better than that. The problem with the compiler taking initiative in the ref vs. value decision is undue aliasing: void fun(const Widget a, Widget b) { ... } In the call fun(x, x) the compiler may or may not alias a with b - a very difficult to detect bug. The two objects don't have to be parameters - one could be e.g. a global. AndreiOn 12/10/10 4:10 PM, foobar wrote:I don't understand this. For sure, const Widget foo(const Widget x) { return x; } is inefficient. But I don't see how problems can ever arise with a function which returns a built-in type (eg, opEquals() ). It seems to me, that the issue relates to deterministic destruction. As I see it, there can be two forms of const parameters: * caller manages lifetime. Caller must call destructor. It must duplicate anything it wants to return. * callee manages lifetime. Callee must destroy the variable, or return it. Interestingly, any parameter marked as 'inout' is the second form. Seems pretty clear to me that opEquals needs the first form. And I think it's a pretty common case: I'm only going to look at this variable, I'm not going to take ownership of it or modify it any way. We have ref const in inout scope 'auto ref' (which does not mean auto + ref). And yet, even with this zoo of modifiers, the best syntax we have for that simple situation is 'auto ref const' ??? We've got to do better than that.Don Wrote:Everyone - please stop suggesting that. It causes severe undue aliasing issues. AndreiSteven Schveighoffer wrote:I agree with don. IMHO, this is incredibly silly given Andrei's use case, since D can have instead: void foo(const Widget); and have an optimization inside the compiler for value types to pass by ref.To summarize for those looking for the C++ behavior, the equivalent would be: void foo(auto ref const Widget)That use of 'auto' is an abomination.
Dec 13 2010
Andrei Alexandrescu wrote:On 12/13/10 9:28 AM, Don wrote:I can't really escape the feeling that 'const' guarantees too little. It makes guarantees to the caller, but tells the callee *nothing*. (Except for the (important) special case where *all* the parameters are const, none have destructors, and the function is pure). I think everything we're actually doing here is trying to tie the semantics down, for the benefit of the callee. So I would think that we need to be very clear about what semantics we can realistically guarantee, and tie the syntax to that. BTW the really big problem I have with 'auto ref' is that it isn't 'auto', and it isn't 'ref'. I wouldn't have the same objection to something like 'autoref'.Andrei Alexandrescu wrote:I agree we should ideally do better than that. The problem with the compiler taking initiative in the ref vs. value decision is undue aliasing: void fun(const Widget a, Widget b) { ... } In the call fun(x, x) the compiler may or may not alias a with b - a very difficult to detect bug. The two objects don't have to be parameters - one could be e.g. a global. AndreiOn 12/10/10 4:10 PM, foobar wrote:I don't understand this. For sure, const Widget foo(const Widget x) { return x; } is inefficient. But I don't see how problems can ever arise with a function which returns a built-in type (eg, opEquals() ). It seems to me, that the issue relates to deterministic destruction. As I see it, there can be two forms of const parameters: * caller manages lifetime. Caller must call destructor. It must duplicate anything it wants to return. * callee manages lifetime. Callee must destroy the variable, or return it. Interestingly, any parameter marked as 'inout' is the second form. Seems pretty clear to me that opEquals needs the first form. And I think it's a pretty common case: I'm only going to look at this variable, I'm not going to take ownership of it or modify it any way. We have ref const in inout scope 'auto ref' (which does not mean auto + ref). And yet, even with this zoo of modifiers, the best syntax we have for that simple situation is 'auto ref const' ??? We've got to do better than that.Don Wrote:Everyone - please stop suggesting that. It causes severe undue aliasing issues. AndreiSteven Schveighoffer wrote:I agree with don. IMHO, this is incredibly silly given Andrei's use case, since D can have instead: void foo(const Widget); and have an optimization inside the compiler for value types to pass by ref.To summarize for those looking for the C++ behavior, the equivalent would be: void foo(auto ref const Widget)That use of 'auto' is an abomination.
Dec 13 2010
Don Wrote:I can't really escape the feeling that 'const' guarantees too little. It makes guarantees to the caller, but tells the callee *nothing*.But it tells the callee exactly what it does, (assuming you unintuitive associate that const objects can be modified). To me const is nothing but a middle man. It allows you to call functions with both immutable and mutable object types. Which is only similar to trusted and similar in goals as templates or even 'auto ref'BTW the really big problem I have with 'auto ref' is that it isn't 'auto', and it isn't 'ref'. I wouldn't have the same objection to something like 'autoref'.I agree here. Makes it seem like you should also have 'auto immutable' and the likes. Maybe there would be reason to look at how we can consolidate all of this. But personally I am not familiar with the problems.
Dec 13 2010
Jesse Phillips wrote:Don Wrote:It says, "you are not allowed to modify this". But it doesn't tell you what it is. Is it immutable? Is it an rvalue? If I write to another variable, will this one change? Will the caller call the destructor for this parameter? You don't know any of these things. Yet, the point of the thread is that sometimes you want to know. But interestingly, if the function is (weakly) pure and has no mutable ref parameters, any const parameter could safely be passed by reference. OTOH one problem I see with 'auto ref' is that it encourages the callee to think that a const parameter won't change during the function. foo(auto ref const X x, ref Y y) // makes it explicit that modifying y might change x. foo(const ref X x, ref Y y) // Modifying y might change x. foo(const X x, ref Y y) // If I modify y, will x change? It can, if x contains a reference type. AFICT, allowing rvalues to implicitly convert to 'const ref' exaccerbates an existing problem, rather than actually creating the problem.I can't really escape the feeling that 'const' guarantees too little. It makes guarantees to the caller, but tells the callee *nothing*.But it tells the callee exactly what it does, (assuming you unintuitive associate that const objects can be modified).To me const is nothing but a middle man. It allows you to call functions with both immutable and mutable object types. Which is only similar to trusted and similar in goals as templates or even 'auto ref'It would be nice if const meant "this value is immutable for the duration of this function call". But I don't think that's possible without expensive deep copying.BTW the really big problem I have with 'auto ref' is that it isn't 'auto', and it isn't 'ref'. I wouldn't have the same objection to something like 'autoref'.I agree here. Makes it seem like you should also have 'auto immutable' and the likes. Maybe there would be reason to look at how we can consolidate all of this. But personally I am not familiar with the problems.
Dec 14 2010
On 2010-12-13 17:54:57 -0500, Don <nospam nospam.com> said:BTW the really big problem I have with 'auto ref' is that it isn't 'auto', and it isn't 'ref'. I wouldn't have the same objection to something like 'autoref'.I don't like "auto ref" as a syntax either, but I also dislike the general direction this solution is leading us to (irrespective of the syntax). One shouldn't have to specify for every function whether the argument should be passed by ref or by copy under the hood. That's just repeating C++ mistake where for certain type you almost always have to use the easy the idiom "const T &" for function parameters. Efficiency should be the default way to pass function parameters around. I made a proposal earlier that instead of having "auto ref" for this we could have a way to define a struct as being automatically passed by ref in function calls. This way you don't have to remember to pass them by "auto ref" to be efficient, it's done automatically. I said earlier that the default way to pass parameters should be efficient, and this is what it allows. Earlier proposal: <http://www.digitalmars.com/pnews/read.php?server=news.digitalmars.com&group=digitalmars.D&artnum=123991> -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Dec 13 2010
On 12/13/2010 2:54 PM, Don wrote:I can't really escape the feeling that 'const' guarantees too little. It makes guarantees to the caller, but tells the callee *nothing*.As far as I'm concerned, that's exactly what I want const for. The caller can rely on the object not being modified. Later, Brad
Dec 13 2010
Brad Roberts wrote:On 12/13/2010 2:54 PM, Don wrote:Yes. But the callee wants some guarantees as well. How can we provide them?I can't really escape the feeling that 'const' guarantees too little. It makes guarantees to the caller, but tells the callee *nothing*.As far as I'm concerned, that's exactly what I want const for. The caller can rely on the object not being modified. Later, Brad
Dec 14 2010
On Mon, 13 Dec 2010 17:54:57 -0500, Don <nospam nospam.com> wrote:I can't really escape the feeling that 'const' guarantees too little. It makes guarantees to the caller, but tells the callee *nothing*.This is the basis of my argument that adding logical const would not compromise the guarantee of const, because it has no guarantees to begin with. But what const *does* do well is give you a good guard-rail to prevent you from making dumb mistakes. Most people are not going to write code that exploits the lack of guarantees, so it's a reasonable constraint. The huge value of const is to unify both mutable and immutable parameters into one function. -Steve
Dec 14 2010
I agree with don. IMHO, this is incredibly silly given Andrei's use case, since D can have instead: void foo(const Widget); and have an optimization inside the compiler for value types to pass by ref. by specifying "const ref" you explicitly require that only a ref to an l-value be provided, whereas without the "ref" an r-value is also allowed a-la c++. much KISSer."auto ref" as a syntax may be not the best choice but the way it solves the problem is very elegant. your "const ref": It doesn't make it KISS. It adds inconsistency (even though it is necessary sometimes, this one is bad). You lose the ability to do the opposite. -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 10 2010
On 2010-12-10 17:12:16 -0500, Don <nospam nospam.com> said:Steven Schveighoffer wrote:One problem I'm starting to realize is that we now have so many available qualifiers for function parameters than it's really easy to get lost. In D1 it was simple: "in" for regular arguments (the default), "inout"/"ref" for passing arguments by refrence, and "out" for output arguments. They all had clear semantics and not too much overlap. In D2, we've lost this simplicity. Add "const/immutable/shared", add "scope", change "in" as an alias for "const scope", give "inout" a totally new meaning, keep "ref" and "out" the same except that now "ref" can be prefixed with "auto" to give it a double meaning... choosing the right modifiers for function parameters is getting extra complicated. Have we lost track of one of D's principles, that doing the right thing should be the easiest way to do things? To me it looks like we're adding more and more ways to pass arguments because the defaults are failing us. Perhaps it's time to revisit how arguments are passed by default. As for "auto ref", if we're to keep it I think it'd be much better if it was a keyword of its own, such as "autoref". Having modifiers is one thing, but having modifiers that apply to modifiers is getting a little hard to parse in my head. This is not unprecedented, in English when one qualifier apply to another and it becomes hard to read we group them by adding a hyphen between the two. -- Michel Fortin michel.fortin michelf.com http://michelf.com/To summarize for those looking for the C++ behavior, the equivalent would be: void foo(auto ref const Widget)That use of 'auto' is an abomination.
Dec 10 2010
On 12/10/10 6:25 PM, Michel Fortin wrote:On 2010-12-10 17:12:16 -0500, Don <nospam nospam.com> said:It's sort of ironic. You just argued for the utility of, and implemented, another type constructor yourself! AndreiSteven Schveighoffer wrote:One problem I'm starting to realize is that we now have so many available qualifiers for function parameters than it's really easy to get lost. In D1 it was simple: "in" for regular arguments (the default), "inout"/"ref" for passing arguments by refrence, and "out" for output arguments. They all had clear semantics and not too much overlap. In D2, we've lost this simplicity. Add "const/immutable/shared", add "scope", change "in" as an alias for "const scope", give "inout" a totally new meaning, keep "ref" and "out" the same except that now "ref" can be prefixed with "auto" to give it a double meaning... choosing the right modifiers for function parameters is getting extra complicated. Have we lost track of one of D's principles, that doing the right thing should be the easiest way to do things? To me it looks like we're adding more and more ways to pass arguments because the defaults are failing us. Perhaps it's time to revisit how arguments are passed by default. As for "auto ref", if we're to keep it I think it'd be much better if it was a keyword of its own, such as "autoref". Having modifiers is one thing, but having modifiers that apply to modifiers is getting a little hard to parse in my head. This is not unprecedented, in English when one qualifier apply to another and it becomes hard to read we group them by adding a hyphen between the two.To summarize for those looking for the C++ behavior, the equivalent would be: void foo(auto ref const Widget)That use of 'auto' is an abomination.
Dec 10 2010
On 2010-12-10 21:28:43 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> said:On 12/10/10 6:25 PM, Michel Fortin wrote:Yeah, I know it's a little ironic. There's a difference though. The problem I'm trying to illustrate here is that you'll need to be an expert to choose the right one depending on the situation. How many times have you seen someone pass std::string by copy in C++? You need a lot of training to get this right all the time because it's not the simpler way to pass parameters. Will the compiler complain when you pass a parameter by value instead of passing it by 'auto ref'? As for the optional 'ref' suffix I added in my patch for tail-const, it's simply the continuation of the same syntax for pointers. It's not a type constructor. It's only a way to make explicit the already-existing implicit reference that classes have so you can apply type constructors separately to it. I doubt people will get it wrong often because in most situations the compiler will complain when you should have made the ref mutable and you haven't. There's no inefficiency by default here. -- Michel Fortin michel.fortin michelf.com http://michelf.com/One problem I'm starting to realize is that we now have so many available qualifiers for function parameters than it's really easy to get lost. In D1 it was simple: "in" for regular arguments (the default), "inout"/"ref" for passing arguments by refrence, and "out" for output arguments. They all had clear semantics and not too much overlap. In D2, we've lost this simplicity. Add "const/immutable/shared", add "scope", change "in" as an alias for "const scope", give "inout" a totally new meaning, keep "ref" and "out" the same except that now "ref" can be prefixed with "auto" to give it a double meaning... choosing the right modifiers for function parameters is getting extra complicated. Have we lost track of one of D's principles, that doing the right thing should be the easiest way to do things? To me it looks like we're adding more and more ways to pass arguments because the defaults are failing us. Perhaps it's time to revisit how arguments are passed by default. As for "auto ref", if we're to keep it I think it'd be much better if it was a keyword of its own, such as "autoref". Having modifiers is one thing, but having modifiers that apply to modifiers is getting a little hard to parse in my head. This is not unprecedented, in English when one qualifier apply to another and it becomes hard to read we group them by adding a hyphen between the two.It's sort of ironic. You just argued for the utility of, and implemented, another type constructor yourself!
Dec 10 2010
On Fri, 10 Dec 2010 21:25:49 -0500 Michel Fortin <michel.fortin michelf.com> wrote:On 2010-12-10 17:12:16 -0500, Don <nospam nospam.com> said: =20uld be:Steven Schveighoffer wrote:To summarize for those looking for the C++ behavior, the equivalent wo=I totally agree. This extends to all sorts of D qualifiers: abstract alias const extern final immutable in inout lazy nothrow out overr= ide private protected public pure ref scope shared static. I'm afraid D2 in on the track of becoming a language for the elite. What do= you think? (I'm certain it is possible to make most languages simpler and as powerful,= if we use clever designer brains with this target in mind. The issue I see= with all those features is: what do they mean? Note What is absent from D = docs is the purpose and meaning of most elements of the language. Probably = obvious for their designers, but who else is supposed to use them?) Denis -- -- -- -- -- -- -- vit esse estrany =E2=98=A3 spir.wikidot.com=20 One problem I'm starting to realize is that we now have so many=20 available qualifiers for function parameters than it's really easy to=20 get lost. =20 In D1 it was simple: "in" for regular arguments (the default),=20 "inout"/"ref" for passing arguments by refrence, and "out" for output=20 arguments. They all had clear semantics and not too much overlap. =20 In D2, we've lost this simplicity. Add "const/immutable/shared", add=20 "scope", change "in" as an alias for "const scope", give "inout" a=20 totally new meaning, keep "ref" and "out" the same except that now=20 "ref" can be prefixed with "auto" to give it a double meaning... =20 choosing the right modifiers for function parameters is getting extra=20 complicated. =20 Have we lost track of one of D's principles, that doing the right thing=20 should be the easiest way to do things? To me it looks like we're=20 adding more and more ways to pass arguments because the defaults are=20 failing us. Perhaps it's time to revisit how arguments are passed by=20 default. =20 As for "auto ref", if we're to keep it I think it'd be much better if=20 it was a keyword of its own, such as "autoref". Having modifiers is one=20 thing, but having modifiers that apply to modifiers is getting a little=20 hard to parse in my head. This is not unprecedented, in English when=20 one qualifier apply to another and it becomes hard to read we group=20 them by adding a hyphen between the two.=20 void foo(auto ref const Widget)=20 That use of 'auto' is an abomination.
Dec 11 2010
On 12/11/10 2:42 CST, spir wrote:On Fri, 10 Dec 2010 21:25:49 -0500 Michel Fortin<michel.fortin michelf.com> wrote:The concern is valid. On the other hand, D is in the business of defining precise interfaces. Let's see how the keywords you mentioned (which are only very loosely related; most are not qualifiers) are up the task: * abstract introduces an entity that is meant to be * alias introduces symbolic names for elaborate constructs. * const specifies that the callee will exact no change on the transitive state of an object. * extern interfaces D with other languages * final prevents (further) overriding of a method * immutable guarantees strong data immutability. * in is a vestige now used for const, and a syntactic component of contracts * inout obviates (modulo current implementation shortcomings) the necessity of defining identical methods, one for each qualifier * lazy sucks * nothrow specifies that a function will never throw * out specifies that the parameter is meant only for output * override is useful to clarify that overriding is intended (btw even C++0x adopted it although it has tried for years to make-do without) * private * protected * public specify access rights * pure guarantees functional semantics for a function * ref indicates pass and return by reference * scope is fuzzy at the time being as a storage class, but is a terrific instruction * shared is instrumental to threading with guarantees * static specifies, well, a number of things :o) We stand to lose the ability to express designs clearly and in good detail whichever of the above we eliminate. What do we take away? AndreiOn 2010-12-10 17:12:16 -0500, Don<nospam nospam.com> said:I totally agree. This extends to all sorts of D qualifiers: abstract alias const extern final immutable in inout lazy nothrow out override private protected public pure ref scope shared static. I'm afraid D2 in on the track of becoming a language for the elite. What do you think? (I'm certain it is possible to make most languages simpler and as powerful, if we use clever designer brains with this target in mind. The issue I see with all those features is: what do they mean? Note What is absent from D docs is the purpose and meaning of most elements of the language. Probably obvious for their designers, but who else is supposed to use them?)Steven Schveighoffer wrote:One problem I'm starting to realize is that we now have so many available qualifiers for function parameters than it's really easy to get lost. In D1 it was simple: "in" for regular arguments (the default), "inout"/"ref" for passing arguments by refrence, and "out" for output arguments. They all had clear semantics and not too much overlap. In D2, we've lost this simplicity. Add "const/immutable/shared", add "scope", change "in" as an alias for "const scope", give "inout" a totally new meaning, keep "ref" and "out" the same except that now "ref" can be prefixed with "auto" to give it a double meaning... choosing the right modifiers for function parameters is getting extra complicated. Have we lost track of one of D's principles, that doing the right thing should be the easiest way to do things? To me it looks like we're adding more and more ways to pass arguments because the defaults are failing us. Perhaps it's time to revisit how arguments are passed by default. As for "auto ref", if we're to keep it I think it'd be much better if it was a keyword of its own, such as "autoref". Having modifiers is one thing, but having modifiers that apply to modifiers is getting a little hard to parse in my head. This is not unprecedented, in English when one qualifier apply to another and it becomes hard to read we group them by adding a hyphen between the two.To summarize for those looking for the C++ behavior, the equivalent would be: void foo(auto ref const Widget)That use of 'auto' is an abomination.
Dec 11 2010
On 12/11/10, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:We stand to lose the ability to express designs clearly and in good detail whichever of the above we eliminate. What do we take away? AndreiNothing. Keep D2 as it is. We have to use D2 for a few years and build some cool apps, before we can figure out if some language feature is truly unnecessary. And that's how we'll know what D3 will look like. I too have found D2's number of keywords a bit too much to digest at first. But then I've realized these keywords are really there to enforce a pattern that was used by convention over the years, e.g. in C++. The keywords are good because: 1) The compiler can help enforce what was only used as a convention before (which is error-prone), and 2) When you read someone else's (or your own) source code you will know for sure what the code actually does, and what it cannot do. I was just reading the GoF book again (I've read it once using C++ years ago) and found it awesome how D directly implements many of the techniques that are enforced by convention in C++. Here was my comment from ycombinator: http://news.ycombinator.com/item?id=1994171
Dec 11 2010
On Fri, 10 Dec 2010 18:28:08 -0800 Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:It's sort of ironic. Tu viens d'implementing yet another type=20 constructor yourself!The need for yet another one signifie s=C3=BBrement (probably means) their = semantics (in the human sense) are wrongly defined. D2 needs un regard neuf= et lucide (a fresh external look) at its whole set of qualifiers. Denis -- -- -- -- -- -- -- vit esse estrany =E2=98=A3 spir.wikidot.com
Dec 11 2010
On 12/11/10 2:49 CST, spir wrote:On Fri, 10 Dec 2010 18:28:08 -0800 Andrei Alexandrescu<SeeWebsiteForEmail erdani.org> wrote:I'm all for it. Even if we can't change the language, I'd like to know "the truth". Because if there's a simple way to go about it that has comparable power, we couldn't find it. AndreiIt's sort of ironic. Tu viens d'implementing yet another type constructor yourself!The need for yet another one signifie sûrement (probably means) their semantics (in the human sense) are wrongly defined. D2 needs un regard neuf et lucide (a fresh external look) at its whole set of qualifiers.
Dec 11 2010
Michel Fortin wrote:On 2010-12-10 17:12:16 -0500, Don <nospam nospam.com> said:Steven Schveighoffer wrote:One problem I'm starting to realize is that we now have so many available qualifiers for function parameters than it's really easy to get lost. In D1 it was simple: "in" for regular arguments (the default), "inout"/"ref" for passing arguments by refrence, and "out" for output arguments. They all had clear semantics and not too much overlap. In D2, we've lost this simplicity. Add "const/immutable/shared", add "scope", change "in" as an alias for "const scope", give "inout" a totally new meaning, keep "ref" and "out" the same except that now "ref" can be prefixed with "auto" to give it a double meaning... choosing the right modifiers for function parameters is getting extra complicated. Have we lost track of one of D's principles, that doing the right thing should be the easiest way to do things? To me it looks like we're adding more and more ways to pass arguments because the defaults are failing us. Perhaps it's time to revisit how arguments are passed by default. As for "auto ref", if we're to keep it I think it'd be much better if it was a keyword of its own, such as "autoref". Having modifiers is one thing, but having modifiers that apply to modifiers is getting a little hard to parse in my head.To summarize for those looking for the C++ behavior, the equivalent would be: void foo(auto ref const Widget)That use of 'auto' is an abomination.This is not unprecedented, in English when one qualifier apply to another and it becomes hard to read we group them by adding a hyphen between the two.The problem is that 'auto' in 'auto ref' has *a contradictory meaning* to every other usage of 'auto' in the language. If we need another keyword, we have to create another keyword. Almost any other syntax would be better. And as far as I can tell, 'auto ref', 'scope' and 'in' as function parameters aren't explained at all in the spec.
Dec 11 2010
On 12/10/2010 10:58 PM, Andrei Alexandrescu wrote:On 12/10/10 12:46 PM, Steven Schveighoffer wrote:Thanks a lot for taking time to explain! Anybody interested see the rationale explained in detail at http://thbecker.net/articles/rvalue_references/section_07.html or http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/On Mon, 06 Dec 2010 08:34:20 -0500, Steven Schveighoffer <schveiguy yahoo.com> wrote:Sorry, indeed I haven't seen it. The problem with binding rvalues to const ref is that once that is in place you have no way to distinguish an rvalue from a const ref on the callee site. If you do want to distinguish, you must rely on complicated conversion priorities. For example, consider: void foo(ref const Widget); void foo(Widget); You'd sometimes want to do that because you want to exploit an rvalue by e.g. moving its state instead of copying it. However, if rvalues become convertible to ref const, then they are motivated to go either way. A rule could be put in place that gives priority to the second declaration. However, things quickly get complicated in the presence of other applicable rules, multiple parameters etc. Essentially it was impossible for C++ to go this way and that's how rvalue references were born. For D I want to avoid all that aggravation and have a simple rule: rvalues don't bind to references to const. If you don't care, use auto ref. This is a simple rule that works promisingly well in various forwarding scenarios. AndreiOn Sun, 05 Dec 2010 09:18:13 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Not sure if this got lost in the noise, I'm still puzzled about this...On 12/5/10 12:04 AM, Steven Schveighoffer wrote:Right. OK, so now I understand what you are saying, but now I don't understand why const ref is such a mistake. Before you explained it was because when you pass an rvalue by ref, it's much more expensive, so auto ref passes by ref if it's an lvalue and by value if it's an rvalue. At least that's what I understood. With const ref, you get the same behavior, plus you are guaranteed that the code isn't going to do something stupid (like modify a value that will be thrown away at the end).I'm totally confused. I thought the point of auto ref was to pass by value if it's an rvalue (since the data is already on the stack). If this is not the case, then why not just make ref work that way? Why wouldn't I mark all my functions as auto ref to avoid being pestered by the compiler?Because you sometimes do care about dealing with a true lvalue. Consider: void bump(ref int x) { ++x; } Then: unsigned int y; bump(y); // you don't want this to go through short z; bump(z); // you don't want this to go through int w; bump(w * 2); // you don't want this to go through
Dec 10 2010
Thanks a lot for taking time to explain! Anybody interested see the rationale explained in detail at http://thbecker.net/articles/rvalue_references/section_07.html or http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/Thanks, I had trouble understanding what this whole rvalue deal is all about.
Dec 10 2010
On 12/11/10, Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:Ah, see, that article talks about RVO - Walter's cool optimization technique. :pThanks a lot for taking time to explain! Anybody interested see the rationale explained in detail at http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/Thanks, I had trouble understanding what this whole rvalue deal is all about.
Dec 10 2010
On 12/11/10, Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:On 12/11/10, Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:P.S. The second link on that page is dead, but I've found a direct link: http://www.elcamino.edu/faculty/gfry/CS2/LValues_RValues.pdfAh, see, that article talks about RVO - Walter's cool optimization technique. :pThanks a lot for taking time to explain! Anybody interested see the rationale explained in detail at http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/Thanks, I had trouble understanding what this whole rvalue deal is all about.
Dec 10 2010
On 12/11/10, Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:On 12/11/10, Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:I hate to spam this topic (last post, I swear), but I find it amusing that Walter created this technique in 1991 and it took Microsoft 12 years to catch up (http://blogs.msdn.com/b/slippman/archive/2004/02/03/66739.aspx).Ah, see, that article talks about RVO - Walter's cool optimization technique. :pThanks a lot for taking time to explain! Anybody interested see the rationale explained in detail at http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/Thanks, I had trouble understanding what this whole rvalue deal is all about.
Dec 10 2010
On 10/12/2010 20:58, Andrei Alexandrescu wrote:On 12/10/10 12:46 PM, Steven Schveighoffer wrote:For some cases, one could just use a differently-named function, instead of overloading it (like 'foo'). That wouldn't solve the issue for generic code (or operator overloading), yes. But I wonder if this is common and important enough (versus the alternative) to merit a new type qualifier or "storage class". If this is really necessary, I think it might be better to have rvalues convertible to ref const, and instead of "auto ref" have a new qualifier that binds *only* to rvalues, like ref(auto) or ref(in) or whatever. This way the more common case is simpler (just "const Widget" instead of "auto ref const Widget", in the case of opEquals for example). And as for distinguishing rvalues, the example above would be: void foo(ref(in) Widget); // exploit rvalue, move state void foo(Widget); which also has the advantage of being able to distinguish the rvalue without making the whole parameter const (which is unnecessarily transitive, unlike C++) -- Bruno Medeiros - Software EngineerOn Mon, 06 Dec 2010 08:34:20 -0500, Steven Schveighoffer <schveiguy yahoo.com> wrote:Sorry, indeed I haven't seen it. The problem with binding rvalues to const ref is that once that is in place you have no way to distinguish an rvalue from a const ref on the callee site. If you do want to distinguish, you must rely on complicated conversion priorities. For example, consider: void foo(ref const Widget); void foo(Widget); You'd sometimes want to do that because you want to exploit an rvalue by e.g. moving its state instead of copying it. However, if rvalues become convertible to ref const, then they are motivated to go either way. A rule could be put in place that gives priority to the second declaration. However, things quickly get complicated in the presence of other applicable rules, multiple parameters etc. Essentially it was impossible for C++ to go this way and that's how rvalue references were born. For D I want to avoid all that aggravation and have a simple rule: rvalues don't bind to references to const. If you don't care, use auto ref. This is a simple rule that works promisingly well in various forwarding scenarios. AndreiOn Sun, 05 Dec 2010 09:18:13 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:
Dec 21 2010
On Sat, 04 Dec 2010 23:36:22 -0500 "Steven Schveighoffer" <schveiguy yahoo.com> wrote:No no no, inout does not belong here. Use const. inout is only used if ==20you are returning a portion of the arguments. That should be a hard rule==20by the compiler (error). =20 Fixed: =20 bool opEquals(auto ref const(Tuple) rhs) constWhy isn't the parameter simply "in" instead of "auto ref const"? (And let t= he compiler decide whther it's worth passing it as ref for efficiency). Denis -- -- -- -- -- -- -- vit esse estrany =E2=98=A3 spir.wikidot.com
Dec 05 2010
Andrei, your explanation is almost the same as was my understanding. Thank you. My shallow thought: const T makes automatically reference. It is convenient. Right thinking: D has no semantics dividing copying/referencing, against has dividing rvalue/lvalue. D should support this like T(copying)/T&(referencing). Thanks. Kenji 2010/12/4 Andrei Alexandrescu <SeeWebsiteForEmail erdani.org>:On 12/4/10 12:42 AM, Don wrote:Officially, opEquals has to have the signature: struct Foo { bool opEquals(const ref Foo x) const {...} }This is a compiler bug. For structs there should be no official implementation of opEquals, opCmp etc. All the compiler needs to worry about is to syntactically translate a == b to a.opEquals(b) and then let the usual language rules resolve the call.But this disallows comparisons with rvalues. eg, Foo bar() { Foo x = 1; return x; } Foo y=1; assert( y == bar() ); // doesn't compile You can get around this by declaring a non-ref opEquals. But this fails if Foo has a destructor. If a struct has a destructor, it cannot be const(this is bug 3606) --- struct S { ~this() {} } void main() { const S z; } --- bug.d(6): Error: destructor bug.S.~this () is not callable using argument types () ------- Likewise, it can't be a const parameter (this is bug 4338). void foo(const S a) {} It works to have it as a const ref parameter. Everything will work if you declare a const ~this(), but that seems a little nonsensical. And you cannot have both const and non-const ~this(). I'm a bit uncertain as to how this is all supposed to work. (1) Should temporaries be allowed to be passed as 'const ref'? (2) If a struct has a destructor, should it be passable as a const parameter? And if so, should the destructor be called?This is a delicate matter that clearly needs a solution. Pass of temporaries by const ref was a huge mistake of C++ that it has paid dearly for and required the introduction of a large complication, the rvalue references feature, to just undo the effects of that mistake. So I don't think we should allow that. Regarding destructors, for every constructed object ever there must be a corresponding destructor call. One issue that has been a matter of debate in C++ has been the fact that any object becomes "deconstified" during destruction. The oddest consequence of that rule is that in C++ you can delete a pointer to a const object: // C++ code class A { ... }; void fun(const A* p) { delete p; /* fine */ } There has been a lot of opposition. const is supposed to limit what you can do with that object, and the fact that you can't invoke certain methods or change members, but you can nuke the entire object, is quite nonintuitive (and leads to a lot of funny real-life comparisons such as "You can go out with my daughter, but no touching. Of course, you can shoot her if you so wish.") In D, the rule must be inferred from D's immutability rules, which pretty much dictate that the destructor must be overloaded for non-const and const (and possibly invariant if the struct needs that). Andrei
Dec 04 2010