www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Destructors, const structs, and opEquals

reply Don <nospam nospam.com> writes:
Officially, opEquals has to have the signature:

struct Foo {
bool opEquals(const ref Foo x) const {...}
}

But this disallows comparisons with rvalues.
eg,

Foo bar() { Foo x = 1; return x; }
Foo y=1;
assert( y == bar() ); // doesn't compile

You can get around this by declaring a non-ref opEquals.
But this fails if Foo has a destructor.

If a struct has a destructor, it cannot be const(this is bug 3606)
---
struct S {
     ~this() {}
}

void main() {
     const S z;
}
---
bug.d(6): Error: destructor bug.S.~this () is not callable using 
argument types ()
-------
Likewise, it can't be a const parameter (this is bug 4338).
void foo(const S a) {}
It works to have it as a const ref parameter.

Everything will work if you declare a const ~this(), but that seems a 
little nonsensical. And you cannot have both const and non-const ~this().

I'm a bit uncertain as to how this is all supposed to work.
(1) Should temporaries be allowed to be passed as 'const ref'?
(2) If a struct has a destructor, should it be passable as a const 
parameter? And if so, should the destructor be called?
Dec 03 2010
next sibling parent reply Franciszek Czekala <home valentimex.com> writes:
 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

 ---
 struct S {
     ~this() {}
 }

 void main() {
     const S z;
 }
 ---
 bug.d(6): Error: destructor bug.S.~this () is not callable using

 -------
 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

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


has: function "=" (X,Y: in T) return Boolean; for every type T, simple or composite, records and classed included. Ada95 has been around for 15 years and did not gain any popularity even though it was described as better than C++. I wish D all best, but in view of the problem signaled in this post the prospects are dim. D seems just a bit too complicated for a compelling replacement of existing languages. Anyway, if struct has value semantics then perhaps the argument to opEquals should have simply 'in' mode? In Ada95 the 'in' mode of the arguments does not determine how the arguments are passed internally to the function. The compiler can choose to pass them by value or by reference as suitable. Since the 'in' mode makes the arguments constant inside, it does not really matter how the arguments are passed, so why burden the user with this knowledge?
Dec 04 2010
parent Olivier Pisano <olivier.pisano laposte.net> writes:
Le 04/12/2010 10:00, Franciszek Czekala a écrit :
 Anyway, if struct has value semantics then perhaps the argument to
 opEquals should have simply 'in' mode? In Ada95 the 'in' mode of
 the arguments does not determine how the arguments are passed
 internally to the function. The compiler can choose to pass them
 by value or by reference as suitable. Since the 'in' mode makes
 the arguments constant inside, it does not really matter how the
 arguments are passed, so why burden the user with this knowledge?

Hi, I am certainly not expert enough to estimate what the consequences such a change would be for the existing code base, but I really appreciate this idea. I suppose a potential problem would be when trying to link code written by different compilers together. In such a scenario, "how the arguments are passed" matters. But I don't know any bit of Ada and don't have a clue about they solved this issue. Cheers, Olivier.
Dec 05 2010
prev sibling next sibling parent spir <denis.spir gmail.com> writes:
On Sat, 4 Dec 2010 09:00:05 +0000 (UTC)
Franciszek Czekala <home valentimex.com> wrote:

 Anyway, if struct has value semantics then perhaps the argument to
 opEquals should have simply 'in' mode? In Ada95 the 'in' mode of
 the arguments does not determine how the arguments are passed
 internally to the function. The compiler can choose to pass them
 by value or by reference as suitable. Since the 'in' mode makes
 the arguments constant inside, it does not really matter how the
 arguments are passed, so why burden the user with this knowledge?

I support this point of view. In general, I think properly chosen qualifier= s should have clear semantics (meaning); then, the language internally may = adopt what is best for simplicity, efficiency, etc... as long as semantics = are maintained. A programmer should not have to care about it (what is wron= g for instance about arrays). Semantics and implementation may be much more= kept apart in D. What is the sense of "in" for an app designer? For the case of "in" (with a value argument), as the argument does not need= to be protected by copy since it is known not to change, the compiler may = chose to pass it by ref when more efficient (structs, mostly). Moreover, wh= y not have "in" be the default for values? What is the meaning of changing = a value parameter?=20 draw (shape, color, position); What is the sense of changing color or position? Qualifiers (semantics in g= eneral) could make more sense. Denis -- -- -- -- -- -- -- vit esse estrany =E2=98=A3 spir.wikidot.com
Dec 04 2010
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday 03 December 2010 22:42:06 Don wrote:
 (1) Should temporaries be allowed to be passed as 'const ref'?

I honestly do not understand why they can't be already. C++ definitely allows this. Is there something bad about it? I'd probably use const ref a lot more, but because it will only take lvalues, it's _highly_ limiting. If you could overload functions on ref (I _think_ that there's a bug on that), then you could have two versions of opEquals - with with const ref and one which would copy the value - but ideally, you'd only need the one with const ref. - Jonathan M Davis
Dec 04 2010
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Dec 04 2010
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Dec 04 2010
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Dec 04 2010
prev sibling next sibling parent so <so so.do> writes:
 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/
Dec 04 2010
prev sibling next sibling parent reply so <so so.do> writes:
 I'm a bit uncertain as to how this is all supposed to work.
 (1) Should temporaries be allowed to be passed as 'const ref'?

Rather, what keeps compiler from interpreting "A fun()" like "const ref A fun()" when it is necessary? After all D's const system has no holes in this case unlike C++. -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 04 2010
parent so <so so.do> writes:
 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/
Dec 04 2010
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
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
Dec 04 2010
prev sibling next sibling parent so <so so.do> writes:
 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/
Dec 04 2010
prev sibling next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/4/10 12:42 AM, Don wrote:
 Officially, opEquals has to have the signature:

 struct Foo {
 bool opEquals(const ref Foo x) const {...}
 }

This is a compiler bug. For structs there should be no official implementation of opEquals, opCmp etc. All the compiler needs to worry about is to syntactically translate a == b to a.opEquals(b) and then let the usual language rules resolve the call.
 But this disallows comparisons with rvalues.
 eg,

 Foo bar() { Foo x = 1; return x; }
 Foo y=1;
 assert( y == bar() ); // doesn't compile

 You can get around this by declaring a non-ref opEquals.
 But this fails if Foo has a destructor.

 If a struct has a destructor, it cannot be const(this is bug 3606)
 ---
 struct S {
 ~this() {}
 }

 void main() {
 const S z;
 }
 ---
 bug.d(6): Error: destructor bug.S.~this () is not callable using
 argument types ()
 -------
 Likewise, it can't be a const parameter (this is bug 4338).
 void foo(const S a) {}
 It works to have it as a const ref parameter.

 Everything will work if you declare a const ~this(), but that seems a
 little nonsensical. And you cannot have both const and non-const ~this().

 I'm a bit uncertain as to how this is all supposed to work.
 (1) Should temporaries be allowed to be passed as 'const ref'?
 (2) If a struct has a destructor, should it be passable as a const
 parameter? And if so, should the destructor be called?

This is a delicate matter that clearly needs a solution. Pass of temporaries by const ref was a huge mistake of C++ that it has paid dearly for and required the introduction of a large complication, the rvalue references feature, to just undo the effects of that mistake. So I don't think we should allow that. Regarding destructors, for every constructed object ever there must be a corresponding destructor call. One issue that has been a matter of debate in C++ has been the fact that any object becomes "deconstified" during destruction. The oddest consequence of that rule is that in C++ you can delete a pointer to a const object: // C++ code class A { ... }; void fun(const A* p) { delete p; /* fine */ } There has been a lot of opposition. const is supposed to limit what you can do with that object, and the fact that you can't invoke certain methods or change members, but you can nuke the entire object, is quite nonintuitive (and leads to a lot of funny real-life comparisons such as "You can go out with my daughter, but no touching. Of course, you can shoot her if you so wish.") In D, the rule must be inferred from D's immutability rules, which pretty much dictate that the destructor must be overloaded for non-const and const (and possibly invariant if the struct needs that). Andrei
Dec 04 2010
next sibling parent reply Don <nospam nospam.com> writes:
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?
Dec 04 2010
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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) foo(const(S) x) { return x; }

 inside foo, a non-const S is created, blitted with x, then postblit is
 called on it, then it is returned as const. On return, the original x is
 destroyed if it was a temporary. Otherwise, it gets called at the end of
 its scope.

 Eg,

 const w = foo(foo( S(2) ));

 would be translated into:
 const w = (foo( S __tmp1 = S(2), const(S) __tmp2 = foo(__tmp1), ~__tmp1,
 __tmp2), ~__tmp2);


 Has this sort of idea been explored? Is there something wrong with it?

What's wrong with it is it consistently leads to suboptimal code, which has created a hecatomb of problems for C++ (even the very carefully conceived rvalue references feature, for all its size and might, is unable to fix them all). The caller should create the copy and pass the responsibility of destroying to the callee. This is because oftentimes the actual object destroyed is not the same object that was constructed. You see those trailing calls ~__tmp1, ~__tmp2 at the end of your code? They are a tin cat stuck to code's tail. Andrei
Dec 04 2010
next sibling parent reply Don <nospam nospam.com> writes:
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 created will also be destroyed, regardless of qualifiers.
 Seems to me that the natural rule would be, that the creator is
 responsible for destruction.

That rule is roughly C++'s and has an issue that D fixes (in my mind; the implementation is not 100% there yet). Exact issue is discussed below.
 This would require that, for example, given
 code like this:

 const(S) foo(const(S) x) { return x; }

 inside foo, a non-const S is created, blitted with x, then postblit is
 called on it, then it is returned as const. On return, the original x is
 destroyed if it was a temporary. Otherwise, it gets called at the end of
 its scope.

 Eg,

 const w = foo(foo( S(2) ));

 would be translated into:
 const w = (foo( S __tmp1 = S(2), const(S) __tmp2 = foo(__tmp1), ~__tmp1,
 __tmp2), ~__tmp2);


 Has this sort of idea been explored? Is there something wrong with it?

What's wrong with it is it consistently leads to suboptimal code, which has created a hecatomb of problems for C++ (even the very carefully conceived rvalue references feature, for all its size and might, is unable to fix them all). The caller should create the copy and pass the responsibility of destroying to the callee. This is because oftentimes the actual object destroyed is not the same object that was constructed. You see those trailing calls ~__tmp1, ~__tmp2 at the end of your code? They are a tin cat stuck to code's tail.

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.
Dec 04 2010
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Dec 04 2010
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/4/10 22:42 CST, Steven Schveighoffer wrote:
 On Sat, 04 Dec 2010 15:58:43 -0500, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 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
Dec 04 2010
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/5/10 12:06 AM, Steven Schveighoffer wrote:
 On Sun, 05 Dec 2010 00:42:20 -0500, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 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
Dec 05 2010
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/4/10 22:36 CST, Steven Schveighoffer wrote:
 On Sat, 04 Dec 2010 11:00:58 -0500, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 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
Dec 04 2010
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/4/10 23:40 CST, Andrei Alexandrescu wrote:
 Yah. Method calls are already passed by reference (I'd prefer not but
 that's just me).

Sorry, I meant: method calls are already allowed for rvalues. Sleepy... Andrei
Dec 04 2010
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/5/10 12:04 AM, Steven Schveighoffer wrote:
 I'm totally confused. I thought the point of auto ref was to pass by
 value if it's an rvalue (since the data is already on the stack). If
 this is not the case, then why not just make ref work that way? Why
 wouldn't I mark all my functions as auto ref to avoid being pestered by
 the compiler?

Because you sometimes do care about dealing with a true lvalue. Consider: void bump(ref int x) { ++x; } Then: unsigned int y; bump(y); // you don't want this to go through short z; bump(z); // you don't want this to go through int w; bump(w * 2); // you don't want this to go through Andrei
Dec 05 2010
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/10/10 12:46 PM, Steven Schveighoffer wrote:
 On Mon, 06 Dec 2010 08:34:20 -0500, Steven Schveighoffer
 <schveiguy yahoo.com> wrote:

 On Sun, 05 Dec 2010 09:18:13 -0500, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 On 12/5/10 12:04 AM, Steven Schveighoffer wrote:
 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
Dec 10 2010
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/10/10 1:10 PM, Steven Schveighoffer wrote:
 On Fri, 10 Dec 2010 15:58:17 -0500, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 On 12/10/10 12:46 PM, Steven Schveighoffer wrote:
 On Mon, 06 Dec 2010 08:34:20 -0500, Steven Schveighoffer
 <schveiguy yahoo.com> wrote:

 On Sun, 05 Dec 2010 09:18:13 -0500, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 On 12/5/10 12:04 AM, Steven Schveighoffer wrote:
 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
Dec 10 2010
parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
On 10/12/2010 21:17, Andrei Alexandrescu wrote:
 We all need to think about this a bit more because it's related to
 another issue that I'm still losing sleep over: should we promote cheap
 copy construction throughout D or not?

I was reminded of another comment that could be said in favor of that: If you look back at your own article and thoughts about ranges and iteration, you made the case for the benefits of the iteration primitives having complexity guarantees (just as is the case with STL). It seems to me that the very same reasoning could be applied to these fundamental type primitives, like the copy constructor at least. If the copy constructor guarantees constant complexity, then other algorithms and operations can be built on top of that, and also provide useful complexity guarantees. Like the sort example you mentioned. (Hum, and if we go this way, it will probably be best not to call it "copy constructor" then) -- Bruno Medeiros - Software Engineer
Dec 21 2010
prev sibling next sibling parent reply Don <nospam nospam.com> writes:
Steven Schveighoffer wrote:
 To summarize for those looking for the C++ behavior, the equivalent 
 would be:
 
 void foo(auto ref const Widget)

That use of 'auto' is an abomination.
Dec 10 2010
next sibling parent reply foobar <foo bar.com> writes:
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.
Dec 10 2010
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Dec 10 2010
next sibling parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2010-12-10 19:32:30 -0500, Andrei Alexandrescu 
<SeeWebsiteForEmail 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.fortin michelf.com http://michelf.com/
Dec 11 2010
parent Michel Fortin <michel.fortin michelf.com> writes:
On 2010-12-11 11:48:36 -0500, spir <denis.spir gmail.com> said:

 If parameters are 'in' or 'const' by default, then whether they are passed
 by value or by ref has no consequence, I guess. The compiler can then safel
 y choose the most efficent more --what it can do as it knows sizeof-- gross
 ly structs by ref, the rest by value. Is this reasoning correct?

No it can't because of aliasing (the object might be referenced by something else). Look at this simple example: struct A { int value; } bool test(const A a1, ref A a2) { ++a2.value; return a1.value == a2.value; } void main() { A a; a.value = 8; bool result = test(a, a); assert(result == false); assert(a.value == 9); } If "a1" is passed by ref under the hood, it can only be done whenever you know for sure there is no aliasing, or if the data is "immutable", otherwise you'll have side effects. So while it could be done in some circumstances (strongly pure functions and immutable parameters), there's still an important need for a more general solution. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Dec 11 2010
prev sibling parent reply Don <nospam nospam.com> writes:
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.
Dec 13 2010
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Dec 13 2010
parent reply Don <nospam nospam.com> writes:
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'.
Dec 13 2010
next sibling parent reply Jesse Phillips <jessekphillips+D gmail.com> writes:
Don Wrote:

 I can't really escape the feeling that 'const' guarantees too little.
 It makes guarantees to the caller, but tells the callee *nothing*.

But it tells the callee exactly what it does, (assuming you unintuitive associate that const objects can be modified). To me const is nothing but a middle man. It allows you to call functions with both immutable and mutable object types. Which is only similar to trusted and similar in goals as templates or even 'auto ref'
 BTW the really big problem I have with 'auto ref' is that it isn't 
 'auto', and it isn't 'ref'. I wouldn't have the same objection to 
 something like 'autoref'.

I agree here. Makes it seem like you should also have 'auto immutable' and the likes. Maybe there would be reason to look at how we can consolidate all of this. But personally I am not familiar with the problems.
Dec 13 2010
parent Don <nospam nospam.com> writes:
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.

Dec 14 2010
prev sibling next sibling parent Michel Fortin <michel.fortin michelf.com> writes:
On 2010-12-13 17:54:57 -0500, Don <nospam nospam.com> said:

 BTW the really big problem I have with 'auto ref' is that it isn't 
 'auto', and it isn't 'ref'. I wouldn't have the same objection to 
 something like 'autoref'.

I don't like "auto ref" as a syntax either, but I also dislike the general direction this solution is leading us to (irrespective of the syntax). One shouldn't have to specify for every function whether the argument should be passed by ref or by copy under the hood. That's just repeating C++ mistake where for certain type you almost always have to use the easy the idiom "const T &" for function parameters. Efficiency should be the default way to pass function parameters around. I made a proposal earlier that instead of having "auto ref" for this we could have a way to define a struct as being automatically passed by ref in function calls. This way you don't have to remember to pass them by "auto ref" to be efficient, it's done automatically. I said earlier that the default way to pass parameters should be efficient, and this is what it allows. Earlier proposal: <http://www.digitalmars.com/pnews/read.php?server=news.digitalmars.com&group=digitalmars.D&artnum=123991> -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Dec 13 2010
prev sibling parent Don <nospam nospam.com> writes:
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?
Dec 14 2010
prev sibling parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2010-12-10 17:12:16 -0500, Don <nospam 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.fortin michelf.com http://michelf.com/
Dec 10 2010
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/10/10 6:25 PM, Michel Fortin wrote:
 On 2010-12-10 17:12:16 -0500, Don <nospam 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
Dec 10 2010
parent Michel Fortin <michel.fortin michelf.com> writes:
On 2010-12-10 21:28:43 -0500, Andrei Alexandrescu 
<SeeWebsiteForEmail 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.fortin michelf.com http://michelf.com/
Dec 10 2010
prev sibling next sibling parent Don <nospam nospam.com> writes:
Michel Fortin wrote:
 On 2010-12-10 17:12:16 -0500, Don <nospam 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.
Dec 11 2010
prev sibling next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/11/10 2:42 CST, spir wrote:
 On Fri, 10 Dec 2010 21:25:49 -0500
 Michel Fortin<michel.fortin michelf.com>  wrote:

 On 2010-12-10 17:12:16 -0500, Don<nospam 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?)

The concern is valid. On the other hand, D is in the business of defining precise interfaces. Let's see how the keywords you mentioned (which are only very loosely related; most are not qualifiers) are up the task: * abstract introduces an entity that is meant to be * alias introduces symbolic names for elaborate constructs. * const specifies that the callee will exact no change on the transitive state of an object. * extern interfaces D with other languages * final prevents (further) overriding of a method * immutable guarantees strong data immutability. * in is a vestige now used for const, and a syntactic component of contracts * inout obviates (modulo current implementation shortcomings) the necessity of defining identical methods, one for each qualifier * lazy sucks * nothrow specifies that a function will never throw * out specifies that the parameter is meant only for output * override is useful to clarify that overriding is intended (btw even C++0x adopted it although it has tried for years to make-do without) * private * protected * public specify access rights * pure guarantees functional semantics for a function * ref indicates pass and return by reference * scope is fuzzy at the time being as a storage class, but is a terrific instruction * shared is instrumental to threading with guarantees * static specifies, well, a number of things :o) We stand to lose the ability to express designs clearly and in good detail whichever of the above we eliminate. What do we take away? Andrei
Dec 11 2010
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 12/11/10 2:49 CST, spir wrote:
 On Fri, 10 Dec 2010 18:28:08 -0800
 Andrei Alexandrescu<SeeWebsiteForEmail erdani.org>  wrote:

 It's sort of ironic. Tu viens d'implementing yet another type
 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
Dec 11 2010
prev sibling next sibling parent Max Samukha <spambox d-coding.com> writes:
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
 <schveiguy yahoo.com> wrote:

 On Sun, 05 Dec 2010 09:18:13 -0500, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 On 12/5/10 12:04 AM, Steven Schveighoffer wrote:
 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/
Dec 10 2010
prev sibling parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
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
 <schveiguy yahoo.com> wrote:

 On Sun, 05 Dec 2010 09:18:13 -0500, Andrei Alexandrescu
 <SeeWebsiteForEmail 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
Dec 21 2010
prev sibling next sibling parent so <so so.do> writes:
 bool opEquals(auto ref inout Tuple rhs) inout {
    foreach (i, T; Types) {
      if (this[i] != rhs[i]) return false;
    }
    return true;
 }

 It looks a bit alembicated but let's not forget that Tuple is supposed  
 to be very flexible and to do a lot of things.

Const-system is a one big abomination, considering the consequences it is quite hard to say it is something good. As complex as it may look, the above example addresses many problems of this system. I would hate to write equal C++ code. Please lets not add any more keyword/syntax, already forgot we had "auto ref"... -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 04 2010
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
Keyword cocktails..

2010/12/4 so <so so.do>:
 bool opEquals(auto ref inout Tuple rhs) inout {
 =A0 foreach (i, T; Types) {
 =A0 =A0 if (this[i] !=3D rhs[i]) return false;
 =A0 }
 =A0 return true;
 }

 It looks a bit alembicated but let's not forget that Tuple is supposed t=


 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 t=

 system. I would hate to write equal C++ code.

 Please lets not add any more keyword/syntax, already forgot we had "auto
 ref"...

 --
 Using Opera's revolutionary email client: http://www.opera.com/mail/

Dec 04 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Sat, 04 Dec 2010 11:00:58 -0500, Andrei Alexandrescu  
<SeeWebsiteForEmail 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
Dec 04 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Sat, 04 Dec 2010 15:58:43 -0500, Andrei Alexandrescu  
<SeeWebsiteForEmail 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.
 [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. -Steve
Dec 04 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Sun, 05 Dec 2010 00:40:26 -0500, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 On 12/4/10 22:36 CST, Steven Schveighoffer wrote:
 On Sat, 04 Dec 2010 11:00:58 -0500, Andrei Alexandrescu
 <SeeWebsiteForEmail 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
Dec 04 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Sun, 05 Dec 2010 00:42:20 -0500, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 Yah, still not getting the original point there.

I responded better in another part of this thread. Basically, inout doesn't mean what you think it means. -Steve
Dec 04 2010
prev sibling next sibling parent spir <denis.spir gmail.com> writes:
On Sat, 04 Dec 2010 23:36:22 -0500
"Steven Schveighoffer" <schveiguy yahoo.com> wrote:

 No no no, inout does not belong here.  Use const.  inout is only used if =

 you are returning a portion of the arguments.  That should be a hard rule=

 by the compiler (error).
=20
 Fixed:
=20
 bool opEquals(auto ref const(Tuple) rhs) const

Why isn't the parameter simply "in" instead of "auto ref const"? (And let t= he compiler decide whther it's worth passing it as ref for efficiency). Denis -- -- -- -- -- -- -- vit esse estrany =E2=98=A3 spir.wikidot.com
Dec 05 2010
prev sibling next sibling parent so <so so.do> writes:
 Huh?  I don't think you understand what I mean.  inout only implicitly  
 converts to const.  Example:

 struct S
 {
    bool opEquals(S rhs){return false;}
 }

 struct T
 {
    S s;
    bool opEquals(auto ref inout T rhs) inout {
       return s == rhs.s; // error, cannot call S.opEquals(S rhs) with  
 parameters (inout S) inout
    }
 }

 You gain nothing from making opEquals of Tuple inout vs. const.

 IMO all opEquals should be const functions, and the parameter should be  
 const if it is marked as ref, or it contains references.

You are right. Reading it again, using inout is wrong here, both as parameter and function qualifier. Only thing missing here is auto ref being broken(?). Using inout in parameter list already giving error here as it should. -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 05 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Sun, 05 Dec 2010 09:18:13 -0500, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 On 12/5/10 12:04 AM, Steven Schveighoffer wrote:
 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
Dec 06 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 06 Dec 2010 08:34:20 -0500, Steven Schveighoffer  
<schveiguy yahoo.com> wrote:

 On Sun, 05 Dec 2010 09:18:13 -0500, Andrei Alexandrescu  
 <SeeWebsiteForEmail erdani.org> wrote:

 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
Dec 10 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 10 Dec 2010 15:58:17 -0500, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 On 12/10/10 12:46 PM, Steven Schveighoffer wrote:
 On Mon, 06 Dec 2010 08:34:20 -0500, Steven Schveighoffer
 <schveiguy yahoo.com> wrote:

 On Sun, 05 Dec 2010 09:18:13 -0500, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 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
Dec 10 2010
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
 Thanks a lot for taking time to explain!

 Anybody interested see the rationale explained in detail at
 http://thbecker.net/articles/rvalue_references/section_07.html or
 http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

Thanks, I had trouble understanding what this whole rvalue deal is all about.
Dec 10 2010
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 12/11/10, Andrej Mitrovic <andrej.mitrovich 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
Dec 10 2010
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 12/11/10, Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:
 On 12/11/10, Andrej Mitrovic <andrej.mitrovich 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
Dec 10 2010
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 12/11/10, Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:
 On 12/11/10, Andrej Mitrovic <andrej.mitrovich 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).
Dec 10 2010
prev sibling next sibling parent so <so so.do> writes:
 I agree with don.
 IMHO, this is incredibly silly given Andrei's use case, since D can have  
 instead:
 void foo(const Widget);
 and have an optimization inside the compiler for value types to pass by  
 ref.

 by specifying "const ref" you explicitly require that only a ref to an  
 l-value be provided, whereas without the "ref" an r-value is also  
 allowed a-la c++.

 much KISSer.

"auto ref" as a syntax may be not the best choice but the way it solves the problem is very elegant. your "const ref": It doesn't make it KISS. It adds inconsistency (even though it is necessary sometimes, this one is bad). You lose the ability to do the opposite. -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 10 2010
prev sibling next sibling parent spir <denis.spir gmail.com> writes:
On Fri, 10 Dec 2010 21:25:49 -0500
Michel Fortin <michel.fortin michelf.com> wrote:

 On 2010-12-10 17:12:16 -0500, Don <nospam nospam.com> said:
=20
 Steven Schveighoffer wrote:
 To summarize for those looking for the C++ behavior, the equivalent wo=



=20
 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=20 available qualifiers for function parameters than it's really easy to=20 get lost. =20 In D1 it was simple: "in" for regular arguments (the default),=20 "inout"/"ref" for passing arguments by refrence, and "out" for output=20 arguments. They all had clear semantics and not too much overlap. =20 In D2, we've lost this simplicity. Add "const/immutable/shared", add=20 "scope", change "in" as an alias for "const scope", give "inout" a=20 totally new meaning, keep "ref" and "out" the same except that now=20 "ref" can be prefixed with "auto" to give it a double meaning... =20 choosing the right modifiers for function parameters is getting extra=20 complicated. =20 Have we lost track of one of D's principles, that doing the right thing=20 should be the easiest way to do things? To me it looks like we're=20 adding more and more ways to pass arguments because the defaults are=20 failing us. Perhaps it's time to revisit how arguments are passed by=20 default. =20 As for "auto ref", if we're to keep it I think it'd be much better if=20 it was a keyword of its own, such as "autoref". Having modifiers is one=20 thing, but having modifiers that apply to modifiers is getting a little=20 hard to parse in my head. This is not unprecedented, in English when=20 one qualifier apply to another and it becomes hard to read we group=20 them by adding a hyphen between the two.

I totally agree. This extends to all sorts of D qualifiers: abstract alias const extern final immutable in inout lazy nothrow out overr= ide private protected public pure ref scope shared static. I'm afraid D2 in on the track of becoming a language for the elite. What do= you think? (I'm certain it is possible to make most languages simpler and as powerful,= if we use clever designer brains with this target in mind. The issue I see= with all those features is: what do they mean? Note What is absent from D = docs is the purpose and meaning of most elements of the language. Probably = obvious for their designers, but who else is supposed to use them?) Denis -- -- -- -- -- -- -- vit esse estrany =E2=98=A3 spir.wikidot.com
Dec 11 2010
prev sibling next sibling parent spir <denis.spir gmail.com> writes:
On Fri, 10 Dec 2010 18:28:08 -0800
Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:

 It's sort of ironic. Tu viens d'implementing yet another type=20
 constructor yourself!

The need for yet another one signifie s=C3=BBrement (probably means) their = semantics (in the human sense) are wrongly defined. D2 needs un regard neuf= et lucide (a fresh external look) at its whole set of qualifiers. Denis -- -- -- -- -- -- -- vit esse estrany =E2=98=A3 spir.wikidot.com
Dec 11 2010
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 12/11/10, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:
 We stand to lose the ability to express designs clearly and in good
 detail whichever of the above we eliminate. What do we take away?


 Andrei

Nothing. Keep D2 as it is. We have to use D2 for a few years and build some cool apps, before we can figure out if some language feature is truly unnecessary. And that's how we'll know what D3 will look like. I too have found D2's number of keywords a bit too much to digest at first. But then I've realized these keywords are really there to enforce a pattern that was used by convention over the years, e.g. in C++. The keywords are good because: 1) The compiler can help enforce what was only used as a convention before (which is error-prone), and 2) When you read someone else's (or your own) source code you will know for sure what the code actually does, and what it cannot do. I was just reading the GoF book again (I've read it once using C++ years ago) and found it awesome how D directly implements many of the techniques that are enforced by convention in C++. Here was my comment from ycombinator: http://news.ycombinator.com/item?id=1994171
Dec 11 2010
prev sibling next sibling parent spir <denis.spir gmail.com> writes:
On Sat, 11 Dec 2010 09:06:33 -0500
Michel Fortin <michel.fortin michelf.com> wrote:

 On 2010-12-10 19:32:30 -0500, Andrei Alexandrescu=20
 <SeeWebsiteForEmail erdani.org> said:
=20
 On 12/10/10 4:10 PM, foobar wrote:
 Don Wrote:
=20
 Steven Schveighoffer wrote:
 To summarize for those looking for the C++ behavior, the equivalent
 would be:
=20
 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=20 have instead: void foo(const Widget); and have an optimization inside the compiler for value types to pass b=



=20
 Everyone - please stop suggesting that. It causes severe undue aliasing=


=20
 If I understand you well Andrei, what you want "auto ref" to be the=20
 same thing as "ref" except that it would also accept rvalues. I think=20
 the reason you want this is because for some types it is more efficient=20
 to pass them as "ref" than by value, so you want to pass them as "ref"=20
 for efficiency and and not necessarily for its semantics. And from=20
 there goes the need for a "ref" that also accepts rvalues.
=20
 I think this is a bad usage of "ref". Efficient should be the way=20
 arguments are passed by default, and modifiers should be used to alter=20
 semantics and not required for efficiency (in most situations). Is=20
 there a way to pass arguments more efficiently without introducing a=20
 bazillion options the programmer then has to choose from?
=20
 Perhaps we're just trying to address the problem from the wrong end.=20
 Instead of having to say for each function parameter how you want it to=20
 be passed, what if the type itself knew how it should be passed as a=20
 parameter?
=20
 	 passbyref struct ArrayOf50 {
 		float[50] content;
 	}
=20
 	string test1(ArrayOf50 a);     // accepts rvalues
 	string test2(ref ArrayOf50 a); // rejects rvalues
=20
 	void main() {
 		test1(ArrayOf50());
 		test2(ArrayOf50()); // error, first argument requires a reference
 	}
=20
 Now, obviously we've given different semantics to the type itself, but=20
 those semantics are going to be consistent and predictable everywhere.=20
 But mostly, you don't have to remember how to pass this struct every=20
 time now. That's a really big gain.
=20
 Also note how you could use this feature to design containers which=20
 don't need to be reference counted but which are still passed=20
 efficiently across function calls.

If parameters are 'in' or 'const' by default, then whether they are passed = by value or by ref has no consequence, I guess. The compiler can then safel= y choose the most efficent more --what it can do as it knows sizeof-- gross= ly structs by ref, the rest by value. Is this reasoning correct? Denis -- -- -- -- -- -- -- vit esse estrany =E2=98=A3 spir.wikidot.com
Dec 11 2010
prev sibling next sibling parent so <so so.do> writes:
 If parameters are 'in' or 'const' by default, then whether they are  
 passed by value or by ref has no consequence, I guess. The compiler can  
 then safely choose the most efficent more --what it can do as it knows  
 sizeof-- grossly structs by ref, the rest by value. Is this reasoning  
 correct?

This kind of design decisions, leaving this responsibility to compiler is very wrong. Also, you are introducing a lock in here, no way to do the opposite. "T& a" "ref T a" are very good tools. You can't always assume compiler always knows the best. "If parameters are in or const" what about if not? Another inconsistency and a quite a bad one at it. -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 11 2010
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 13 Dec 2010 17:54:57 -0500, Don <nospam nospam.com> wrote:

 I can't really escape the feeling that 'const' guarantees too little.
 It makes guarantees to the caller, but tells the callee *nothing*.

This is the basis of my argument that adding logical const would not compromise the guarantee of const, because it has no guarantees to begin with. But what const *does* do well is give you a good guard-rail to prevent you from making dumb mistakes. Most people are not going to write code that exploits the lack of guarantees, so it's a reasonable constraint. The huge value of const is to unify both mutable and immutable parameters into one function. -Steve
Dec 14 2010
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
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
Dec 04 2010
prev sibling next sibling parent so <so so.do> writes:
On Sat, 04 Dec 2010 16:05:07 +0200, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 On 12/4/10 6:50 AM, so wrote:
 I'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/
Dec 04 2010
prev sibling next sibling parent so <so so.do> writes:
 i would take take the address of a temporary function...

Should be: i would take the address of a temporary object/value. -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Dec 04 2010
prev sibling next sibling parent kenji hara <k.hara.pg gmail.com> writes:
Andrei, your explanation is almost the same as was my understanding. Thank you.

My shallow thought:
  const T makes automatically reference. It is convenient.
Right thinking:
  D has no semantics dividing copying/referencing, against has
dividing rvalue/lvalue.
  D should support this like T(copying)/T&(referencing).

Thanks.

Kenji

2010/12/4 Andrei Alexandrescu <SeeWebsiteForEmail erdani.org>:
 On 12/4/10 12:42 AM, Don wrote:
 Officially, opEquals has to have the signature:

 struct Foo {
 bool opEquals(const ref Foo x) const {...}
 }

This is a compiler bug. For structs there should be no official implementation of opEquals, opCmp etc. All the compiler needs to worry about is to syntactically translate a == b to a.opEquals(b) and then let the usual language rules resolve the call.
 But this disallows comparisons with rvalues.
 eg,

 Foo bar() { Foo x = 1; return x; }
 Foo y=1;
 assert( y == bar() ); // doesn't compile

 You can get around this by declaring a non-ref opEquals.
 But this fails if Foo has a destructor.

 If a struct has a destructor, it cannot be const(this is bug 3606)
 ---
 struct S {
 ~this() {}
 }

 void main() {
 const S z;
 }
 ---
 bug.d(6): Error: destructor bug.S.~this () is not callable using
 argument types ()
 -------
 Likewise, it can't be a const parameter (this is bug 4338).
 void foo(const S a) {}
 It works to have it as a const ref parameter.

 Everything will work if you declare a const ~this(), but that seems a
 little nonsensical. And you cannot have both const and non-const ~this().

 I'm a bit uncertain as to how this is all supposed to work.
 (1) Should temporaries be allowed to be passed as 'const ref'?
 (2) If a struct has a destructor, should it be passable as a const
 parameter? And if so, should the destructor be called?

This is a delicate matter that clearly needs a solution. Pass of temporaries by const ref was a huge mistake of C++ that it has paid dearly for and required the introduction of a large complication, the rvalue references feature, to just undo the effects of that mistake. So I don't think we should allow that. Regarding destructors, for every constructed object ever there must be a corresponding destructor call. One issue that has been a matter of debate in C++ has been the fact that any object becomes "deconstified" during destruction. The oddest consequence of that rule is that in C++ you can delete a pointer to a const object: // C++ code class A { ... }; void fun(const A* p) { delete p; /* fine */ } There has been a lot of opposition. const is supposed to limit what you can do with that object, and the fact that you can't invoke certain methods or change members, but you can nuke the entire object, is quite nonintuitive (and leads to a lot of funny real-life comparisons such as "You can go out with my daughter, but no touching. Of course, you can shoot her if you so wish.") In D, the rule must be inferred from D's immutability rules, which pretty much dictate that the destructor must be overloaded for non-const and const (and possibly invariant if the struct needs that). Andrei

Dec 04 2010
prev sibling parent Brad Roberts <braddr puremagic.com> writes:
On 12/13/2010 2:54 PM, Don wrote:

 I can't really escape the feeling that 'const' guarantees too little.
 It makes guarantees to the caller, but tells the callee *nothing*.

As far as I'm concerned, that's exactly what I want const for. The caller can rely on the object not being modified. Later, Brad
Dec 13 2010