Re: Destructors, const structs, and opEquals
On 10/12/2010 20:58, Andrei Alexandrescu wrote: On 12/10/10 12:46 PM, Steven Schveighoffer wrote: On Mon, 06 Dec 2010 08:34:20 -0500, Steven Schveighoffer schvei...@yahoo.com wrote: On Sun, 05 Dec 2010 09:18:13 -0500, Andrei Alexandrescu seewebsiteforem...@erdani.org 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. Andrei 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 Engineer
Re: Destructors, const structs, and opEquals
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
Re: Destructors, const structs, and opEquals
Brad Roberts wrote: 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 Yes. But the callee wants some guarantees as well. How can we provide them?
Re: Destructors, const structs, and opEquals
Jesse Phillips wrote: 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). 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. 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.
Re: Destructors, const structs, and opEquals
On Mon, 13 Dec 2010 17:54:57 -0500, Don nos...@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
Re: Destructors, const structs, and opEquals
Andrei Alexandrescu wrote: On 12/10/10 4:10 PM, foobar wrote: Don Wrote: 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. 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. Everyone - please stop suggesting that. It causes severe undue aliasing issues. Andrei 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.
Re: Destructors, const structs, and opEquals
On 12/13/10 9:28 AM, Don wrote: Andrei Alexandrescu wrote: On 12/10/10 4:10 PM, foobar wrote: Don Wrote: 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. 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. Everyone - please stop suggesting that. It causes severe undue aliasing issues. Andrei 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. 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. Andrei
Re: Destructors, const structs, and opEquals
Andrei Alexandrescu wrote: On 12/13/10 9:28 AM, Don wrote: Andrei Alexandrescu wrote: On 12/10/10 4:10 PM, foobar wrote: Don Wrote: 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. 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. Everyone - please stop suggesting that. It causes severe undue aliasing issues. Andrei 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. 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. Andrei 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'.
Re: Destructors, const structs, and opEquals
On 2010-12-13 17:54:57 -0500, Don nos...@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.comgroup=digitalmars.Dartnum=123991 -- Michel Fortin michel.for...@michelf.com http://michelf.com/
Re: Destructors, const structs, and opEquals
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
For whom is (was: D2? Re: Destructors, const structs, and opEquals)
On Fri, 10 Dec 2010 21:25:49 -0500 Michel Fortin michel.for...@michelf.com wrote: On 2010-12-10 17:12:16 -0500, Don nos...@nospam.com said: 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. 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. 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?) Denis -- -- -- -- -- -- -- vit esse estrany ☣ spir.wikidot.com
Re: Destructors, const structs, and opEquals
On Fri, 10 Dec 2010 18:28:08 -0800 Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: It'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. Denis -- -- -- -- -- -- -- vit esse estrany ☣ spir.wikidot.com
Re: Destructors, const structs, and opEquals
Michel Fortin wrote: On 2010-12-10 17:12:16 -0500, Don nos...@nospam.com said: 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. 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. 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.
Re: Destructors, const structs, and opEquals
On 2010-12-10 19:32:30 -0500, Andrei Alexandrescu seewebsiteforem...@erdani.org said: On 12/10/10 4:10 PM, foobar wrote: Don Wrote: 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. 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. Everyone - please stop suggesting that. It causes severe undue aliasing issues. 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.for...@michelf.com http://michelf.com/
Re: Destructors, const structs, and opEquals
On 12/11/10 2:49 CST, spir wrote: On Fri, 10 Dec 2010 18:28:08 -0800 Andrei Alexandrescuseewebsiteforem...@erdani.org wrote: It'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. 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. Andrei
Re: Destructors, const structs, and opEquals
On Sat, 11 Dec 2010 09:06:33 -0500 Michel Fortin michel.for...@michelf.com wrote: On 2010-12-10 19:32:30 -0500, Andrei Alexandrescu seewebsiteforem...@erdani.org said: On 12/10/10 4:10 PM, foobar wrote: Don Wrote: 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. 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. Everyone - please stop suggesting that. It causes severe undue aliasing issues. 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. 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? Denis -- -- -- -- -- -- -- vit esse estrany ☣ spir.wikidot.com
Re: Destructors, const structs, and opEquals
On 2010-12-11 11:48:36 -0500, spir denis.s...@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.for...@michelf.com http://michelf.com/
Re: Destructors, const structs, and opEquals
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/
Re: Destructors, const structs, and opEquals
On Mon, 06 Dec 2010 08:34:20 -0500, Steven Schveighoffer schvei...@yahoo.com wrote: On Sun, 05 Dec 2010 09:18:13 -0500, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: 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 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). Not sure if this got lost in the noise, I'm still puzzled about this... -Steve
Re: Destructors, const structs, and opEquals
On 12/10/10 12:46 PM, Steven Schveighoffer wrote: On Mon, 06 Dec 2010 08:34:20 -0500, Steven Schveighoffer schvei...@yahoo.com wrote: On Sun, 05 Dec 2010 09:18:13 -0500, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: 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 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). Not sure if this got lost in the noise, I'm still puzzled about this... 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. Andrei
Re: Destructors, const structs, and opEquals
On Fri, 10 Dec 2010 15:58:17 -0500, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: On 12/10/10 12:46 PM, Steven Schveighoffer wrote: On Mon, 06 Dec 2010 08:34:20 -0500, Steven Schveighoffer schvei...@yahoo.com wrote: On Sun, 05 Dec 2010 09:18:13 -0500, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: 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 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). Not sure if this got lost in the noise, I'm still puzzled about this... 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. 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? -Steve
Re: Destructors, const structs, and opEquals
On 12/10/10 1:10 PM, Steven Schveighoffer wrote: On Fri, 10 Dec 2010 15:58:17 -0500, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: On 12/10/10 12:46 PM, Steven Schveighoffer wrote: On Mon, 06 Dec 2010 08:34:20 -0500, Steven Schveighoffer schvei...@yahoo.com wrote: On Sun, 05 Dec 2010 09:18:13 -0500, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: 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 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). Not sure if this got lost in the noise, I'm still puzzled about this... 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. 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? 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? Andrei
Re: Destructors, const structs, and opEquals
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.
Re: Destructors, const structs, and opEquals
On 12/10/2010 10:58 PM, Andrei Alexandrescu wrote: On 12/10/10 12:46 PM, Steven Schveighoffer wrote: On Mon, 06 Dec 2010 08:34:20 -0500, Steven Schveighoffer schvei...@yahoo.com wrote: On Sun, 05 Dec 2010 09:18:13 -0500, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: 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 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). Not sure if this got lost in the noise, I'm still puzzled about this... 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. Andrei 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/
Re: Destructors, const structs, and opEquals
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.
Re: Destructors, const structs, and opEquals
On 12/11/10, Andrej Mitrovic andrej.mitrov...@gmail.com wrote: Thanks 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. Ah, see, that article talks about RVO - Walter's cool optimization technique. :p
Re: Destructors, const structs, and opEquals
On 12/11/10, Andrej Mitrovic andrej.mitrov...@gmail.com wrote: On 12/11/10, Andrej Mitrovic andrej.mitrov...@gmail.com wrote: Thanks 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. Ah, see, that article talks about RVO - Walter's cool optimization technique. :p 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.pdf
Re: Destructors, const structs, and opEquals
On 12/11/10, Andrej Mitrovic andrej.mitrov...@gmail.com wrote: On 12/11/10, Andrej Mitrovic andrej.mitrov...@gmail.com wrote: Thanks 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. Ah, see, that article talks about RVO - Walter's cool optimization technique. :p 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).
Re: Destructors, const structs, and opEquals
Don Wrote: 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. 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.
Re: Destructors, const structs, and opEquals
On 12/10/10 4:10 PM, foobar wrote: Don Wrote: 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. 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. Everyone - please stop suggesting that. It causes severe undue aliasing issues. Andrei
Re: Destructors, const structs, and opEquals
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/
Re: Destructors, const structs, and opEquals
On 2010-12-10 17:12:16 -0500, Don nos...@nospam.com said: 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. 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.for...@michelf.com http://michelf.com/
Re: Destructors, const structs, and opEquals
On 12/10/10 6:25 PM, Michel Fortin wrote: On 2010-12-10 17:12:16 -0500, Don nos...@nospam.com said: 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. 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! Andrei
Re: Destructors, const structs, and opEquals
On 12/10/10 6:25 PM, Michel Fortin wrote: On 2010-12-10 17:12:16 -0500, Don nos...@nospam.com said: 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. 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. Tu viens d'implementing yet another type constructor yourself! Andrei
Re: Destructors, const structs, and opEquals
On 2010-12-10 21:28:43 -0500, Andrei Alexandrescu seewebsiteforem...@erdani.org said: On 12/10/10 6:25 PM, Michel Fortin 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. It's sort of ironic. You just argued for the utility of, and implemented, another type constructor yourself! 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.for...@michelf.com http://michelf.com/
Re: Destructors, const structs, and opEquals
On Sun, 05 Dec 2010 09:18:13 -0500, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: 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 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). -Steve
Re: Destructors, const structs, and opEquals
On Sat, 04 Dec 2010 23:36:22 -0500 Steven Schveighoffer schvei...@yahoo.com 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) const Why isn't the parameter simply in instead of auto ref const? (And let the compiler decide whther it's worth passing it as ref for efficiency). Denis -- -- -- -- -- -- -- vit esse estrany ☣ spir.wikidot.com
Re: Destructors, const structs, and opEquals
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.
Re: Destructors, const structs, and opEquals
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/
Re: Destructors, const structs, and opEquals
On 12/5/10 12:06 AM, Steven Schveighoffer wrote: On Sun, 05 Dec 2010 00:42:20 -0500, Andrei Alexandrescu seewebsiteforem...@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. 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. Andrei
Re: Destructors, const structs, and opEquals
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
Re: Destructors, const structs, and opEquals
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? 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?
in and argument passing (was Re: Destructors, const structs, and opEquals)
On Sat, 4 Dec 2010 09:00:05 + (UTC) Franciszek Czekala h...@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 qualifiers 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 wrong 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, why not have in be the default for values? What is the meaning of changing a value parameter? draw (shape, color, position); What is the sense of changing color or position? Qualifiers (semantics in general) could make more sense. Denis -- -- -- -- -- -- -- vit esse estrany ☣ spir.wikidot.com
Re: Destructors, const structs, and opEquals
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
Re: Destructors, const structs, and opEquals
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. 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/
Re: Destructors, const structs, and opEquals
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/
Re: Destructors, const structs, and opEquals
On Saturday 04 December 2010 03:49:26 so wrote: 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. If you don't mean new C++ standards, this is not true. It is supported by non-standard extensions. 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 Davis
Re: Destructors, const structs, and opEquals
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 Oh 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/
Re: Destructors, const structs, and opEquals
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'? 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/
Re: Destructors, const structs, and opEquals
On 12/4/10 4:35 AM, Jonathan M Davis wrote: 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. C++'s second biggest mistake. Andrei
Re: Destructors, const structs, and opEquals
On 12/4/10 6:50 AM, 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 Oh 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. 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 Andrei
Re: Destructors, const structs, and opEquals
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
Re: Destructors, const structs, and opEquals
On Saturday 04 December 2010 06:00:58 Andrei Alexandrescu wrote: On 12/4/10 4:35 AM, Jonathan M Davis wrote: 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. C++'s second biggest mistake. 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 Davis
Re: Destructors, const structs, and opEquals
On Sat, 04 Dec 2010 16:05:07 +0200, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: On 12/4/10 6:50 AM, 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 Oh 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. 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 Andrei 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/
Re: Destructors, const structs, and opEquals
On 12/4/10 8:29 AM, Jonathan M Davis wrote: On Saturday 04 December 2010 06:00:58 Andrei Alexandrescu wrote: On 12/4/10 4:35 AM, Jonathan M Davis wrote: 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. C++'s second biggest mistake. 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? It makes it impossible to distinguish an rvalue from an lvalue. Andrei
Re: Destructors, const structs, and opEquals
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/
Re: Destructors, const structs, and opEquals
Andrei Alexandrescu wrote: 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. 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? 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). 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?
Re: Destructors, const structs, and opEquals
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 seewebsiteforem...@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
Re: Destructors, const structs, and opEquals
On 12/4/10 9:23 AM, Don wrote: Andrei Alexandrescu wrote: 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. 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. 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. 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. 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). This scares me. I can see a danger of making structs with destructors practically unusable. 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. 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)
Re: Destructors, const structs, and opEquals
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/
Re: Destructors, const structs, and opEquals
Keyword cocktails.. 2010/12/4 so s...@so.do: 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/
Re: Destructors, const structs, and opEquals
Andrei Alexandrescu wrote: On 12/4/10 9:23 AM, Don wrote: Andrei Alexandrescu wrote: 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. 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. 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. 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? 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. 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). This scares me. I can see a danger of making structs with destructors practically unusable. 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. 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
Re: Destructors, const structs, and opEquals
On 12/4/10 2:39 PM, Don wrote: Andrei Alexandrescu wrote: On 12/4/10 9:23 AM, Don wrote: Andrei Alexandrescu wrote: 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. 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. 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. 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? 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] 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. 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. 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. Andrei
Re: Destructors, const structs, and opEquals
On Sat, 04 Dec 2010 11:00:58 -0500, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: On 12/4/10 9:23 AM, Don wrote: Andrei Alexandrescu wrote: 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. 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. 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; } 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) const 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. 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'. -Steve
Re: Destructors, const structs, and opEquals
On 12/4/10 22:42 CST, Steven Schveighoffer wrote: On Sat, 04 Dec 2010 15:58:43 -0500, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: On 12/4/10 2:39 PM, Don wrote: Andrei Alexandrescu wrote: On 12/4/10 9:23 AM, Don wrote: Andrei Alexandrescu wrote: 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. 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. 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. 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? 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. You have not addressed that problem -- tack an inout on it, everybody must define opEquals with inout. I don't think so. On the contrary, declaring with inout is the most adaptive. [snip] 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. 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. 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. 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. Yah, still not getting the original point there. Andrei
Re: Destructors, const structs, and opEquals
On 12/4/10 22:36 CST, Steven Schveighoffer wrote: On Sat, 04 Dec 2010 11:00:58 -0500, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: On 12/4/10 9:23 AM, Don wrote: Andrei Alexandrescu wrote: 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. 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. 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; } 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) const Then you handle the angry crowds for me please. 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. But it must instantiate two functions, no? No. 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
Re: Destructors, const structs, and opEquals
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
Re: Destructors, const structs, and opEquals
On Sun, 05 Dec 2010 00:40:26 -0500, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: On 12/4/10 22:36 CST, Steven Schveighoffer wrote: On Sat, 04 Dec 2010 11:00:58 -0500, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: On 12/4/10 9:23 AM, Don wrote: Andrei Alexandrescu wrote: 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. 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. 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; } 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) const Then you handle the angry crowds for me please. 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. 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. But it must instantiate two functions, no? No. How does one call the same function with by ref or by value? By always using ref. 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? -Steve
Re: Destructors, const structs, and opEquals
On Sun, 05 Dec 2010 00:42:20 -0500, Andrei Alexandrescu seewebsiteforem...@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