digitalmars.D - disable all member function calls for rvalues?
- Andrei Alexandrescu (33/33) Dec 17 2009 There is a wart in D I'd like to eliminate. Unfortunately, that would
- dsimcha (26/28) Dec 17 2009 This is the problem, with this feature and with a lot of other "safety" ...
- Jason House (5/40) Dec 17 2009 immutable functions too.
- dsimcha (16/18) Dec 17 2009 I'll say. One more example that I forgot to include, but I think is pre...
- Michel Fortin (39/79) Dec 18 2009 Inoperant? Not necessarily. Assigning to temporaries like that is used
- Kagamin (2/3) Dec 18 2009 The problem here is not in passing temporaries as this, the problem is t...
- Kagamin (2/7) Dec 18 2009 I think, possible solution is to disallow assign operations on the retur...
- Andrei Alexandrescu (5/13) Dec 18 2009 Won't help. Consider:
- Kagamin (2/9) Dec 22 2009 In contrast with the original example this is not a bug.
- Andrei Alexandrescu (6/16) Dec 22 2009 I think it is to the extent (a) it does nothing (b) the syntactic
- Steven Schveighoffer (5/22) Dec 22 2009 What if a.b returns a Rebindable!(const(T)) rvalue, and the rest are
- Steven Schveighoffer (10/32) Dec 22 2009 I just realized in my efforts to use a real phobos-supported wrapper typ...
- Michel Fortin (12/17) Dec 22 2009 What you need is tail const. Lvalues are similar to tail-const values:
- Steven Schveighoffer (31/44) Dec 22 2009 You mean head-const. We have tail-const already (I often get them
- Kagamin (14/20) Dec 23 2009 class File
- Andrei Alexandrescu (7/32) Dec 23 2009 I didn't think that's invalid. I was talking about the other example
- Michel Fortin (16/22) Dec 23 2009 Is this one of them?
- Andrei Alexandrescu (4/29) Dec 23 2009 That's just mean! :o)
- Michel Fortin (14/33) Dec 24 2009 Well, if we had a way for saying we want to pass the "this" argument by
- Kagamin (2/3) Dec 18 2009 Yesterday I hit bug 1893 and I won't apreciate any extra requirement for...
- Kagamin (2/15) Dec 25 2009 So you deliberately want to break consistency between fields and propert...
- Andrei Alexandrescu (7/25) Dec 25 2009 Properties that return rvalues cannot be fully consistent with fields
- Michel Fortin (15/17) Dec 25 2009 If you could pass "this" by value instead of ref, it'd solve the
- Andrei Alexandrescu (4/21) Dec 25 2009 I've seen that. The only problem I see with that is that it complicates
- Michel Fortin (11/26) Dec 25 2009 If we ever get that "universal calling syntax" thing, where any
There is a wart in D I'd like to eliminate. Unfortunately, that would also eliminate some valid uses. The problem is as follows. "ref" values in function parameter lists are always bound to lvalues - never rvalues. This is, except for _one_ case: "this" in member functions. struct A { void fun() { ... } } A gun() { return A(); } unittest { gun().fun(); // works } This doesn't sound so bad until the following happens: struct A { ref A opAssign(ref A rhs) { ... } } struct B { property A a() { ... } } unittest { B b; b.a = A.init; } Everything looks kosher, but the problem is b.a returns a temporary, and then that temporary is assigned to. The assignment is inoperant. For this and other related reasons I'd like to disallow binding temporaries to "this". There are cases in which you'd want to, and you'd have to insert explicit variables. But I think eliminating the binding is the right thing to do. Thoughts? Andrei
Dec 17 2009
== Quote from Andrei Alexandrescu (SeeWebsiteForEmail erdani.org)'s articleThere is a wart in D I'd like to eliminate. Unfortunately, that would also eliminate some valid uses.This is the problem, with this feature and with a lot of other "safety" features that have been considered. Having lots of static checking, etc. is good if and only if it doesn't get in the way too much when the programmer **does** know what he/she is doing. Yes, this limitation would prevent some bugs, but it would get in the way very frequently as well and be an extremely annoying piece of syntactic salt. Given the choice I'd rather have to debug a few more bugs in corner cases than have the language reject lots of valid cases. Of course, there's room for intermediate solutions: 1. Allow member function calls iff the function is const. For example, the following would still work: struct A { string toString() const { return "A"; } } struct B { A getA() property { return A.init; } } B b; string s = b.getA().toString(); 2. Special case opAssign, since this clearly makes no sense for rvalues. 3. If consistency is the main concern, make other ref rvalue binding more permissive instead.
Dec 17 2009
dsimcha Wrote:== Quote from Andrei Alexandrescu (SeeWebsiteForEmail erdani.org)'s articleimmutable functions too.There is a wart in D I'd like to eliminate. Unfortunately, that would also eliminate some valid uses.This is the problem, with this feature and with a lot of other "safety" features that have been considered. Having lots of static checking, etc. is good if and only if it doesn't get in the way too much when the programmer **does** know what he/she is doing. Yes, this limitation would prevent some bugs, but it would get in the way very frequently as well and be an extremely annoying piece of syntactic salt. Given the choice I'd rather have to debug a few more bugs in corner cases than have the language reject lots of valid cases. Of course, there's room for intermediate solutions: 1. Allow member function calls iff the function is const. For example, the following would still work: struct A { string toString() const { return "A"; } }struct B { A getA() property { return A.init; } } B b; string s = b.getA().toString(); 2. Special case opAssign, since this clearly makes no sense for rvalues. 3. If consistency is the main concern, make other ref rvalue binding more permissive instead.An alternate I was thinking about was disallowing non-const/immutable functions with void return type. They seem the most probable to be doing bad things. Some may want the read portion of a property to be callable on an rvalue. That could allow use of "logical const" by convention...
Dec 17 2009
== Quote from Andrei Alexandrescu (SeeWebsiteForEmail erdani.org)'s articleThere is a wart in D I'd like to eliminate. Unfortunately, that would also eliminate some valid uses.I'll say. One more example that I forgot to include, but I think is pretty important: struct CheckedInt { int num; /// Rest of implementation. bool opEquals(CheckedInt rhs) const { return num == rhs.num; } } CheckedInt fun() { return CheckedInt(5); } assert(fun() == CheckedInt(5)); // Wouldn't compile. Requiring an explicit temp variable here is so utterly ridiculous that, if D required one, it would rightfully be made the laughing stock among programming languages.
Dec 17 2009
On 2009-12-17 23:57:09 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> said:There is a wart in D I'd like to eliminate. Unfortunately, that would also eliminate some valid uses. The problem is as follows. "ref" values in function parameter lists are always bound to lvalues - never rvalues. This is, except for _one_ case: "this" in member functions. struct A { void fun() { ... } } A gun() { return A(); } unittest { gun().fun(); // works } This doesn't sound so bad until the following happens: struct A { ref A opAssign(ref A rhs) { ... } } struct B { property A a() { ... } } unittest { B b; b.a = A.init; } Everything looks kosher, but the problem is b.a returns a temporary, and then that temporary is assigned to. The assignment is inoperant.Inoperant? Not necessarily. Assigning to temporaries like that is used sometime in C++ when a temporary "proxy" object is returned, the temporary object just forwarding the assignment to the right place. Calling any function on a temporary could have "side effects", and those "side effects" might effectively be the main desired effect. Which makes me think that the same applies to rvalues being bound to ref: they could be temporary proxies too, or other types with side effects.For this and other related reasons I'd like to disallow binding temporaries to "this". There are cases in which you'd want to, and you'd have to insert explicit variables. But I think eliminating the binding is the right thing to do.I agree with you that the dissimilarity for this and other arguments is not desirable. Personally, I would allow rvalues to convert to non-const ref. There are as many valid uses for temporaries passed as arguments (like the proxy object) than there are for temporaries passed as "this". If you really want to disallow ref temporaries, then I'd suggest having some kind of flag allowing us to opt-in specific types. Only those types would be allowed as non-const ref temporaries. But it seem to me that we already have enough flags to deal with in D. Which makes me think of another problem with forcing const: unlike C++, our const is transitive. This means that if your temporary is just some kind of smart pointer to somewhere, forcing it to be const would prevent any modification of the data it points to. Simple synthetic example: RefCountedPtr!MyObject getAnObject(); void test(ref RefCountedPtr!MyObject object) { object.mutate(); // method that requires a mutable object } test(getAnObject); Anything of the sort can't work if you force the argument to be const. In C++ you can make ref const with not much consequence because const is not transitive. But not in D. I'm all for detecting unintended uses of rvalues, but I think forcing ref to be const is a mistake, both for "this" and other arguments. We need another solution. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Dec 18 2009
Andrei Alexandrescu Wrote:b.a = A.init;The problem here is not in passing temporaries as this, the problem is that property assignment is ambiguous with opAssign called on the property's return value. Your proposed solution seems unrelated to the problem.
Dec 18 2009
Kagamin Wrote:Andrei Alexandrescu Wrote:I think, possible solution is to disallow assign operations on the return value: =, +=, *=, ~=, ++ etc, but read operations should be allowed and functions should be callable by their symbolical names.b.a = A.init;The problem here is not in passing temporaries as this, the problem is that property assignment is ambiguous with opAssign called on the property's return value. Your proposed solution seems unrelated to the problem.
Dec 18 2009
Kagamin wrote:Kagamin Wrote:Won't help. Consider: a.b.c.d.e.f = g; If anywhere on the path an rvalue is created, the code is useless busywork. AndreiAndrei Alexandrescu Wrote:I think, possible solution is to disallow assign operations on the return value: =, +=, *=, ~=, ++ etc, but read operations should be allowed and functions should be callable by their symbolical names.b.a = A.init;The problem here is not in passing temporaries as this, the problem is that property assignment is ambiguous with opAssign called on the property's return value. Your proposed solution seems unrelated to the problem.
Dec 18 2009
Andrei Alexandrescu Wrote:In contrast with the original example this is not a bug.I think, possible solution is to disallow assign operations on the return value: =, +=, *=, ~=, ++ etc, but read operations should be allowed and functions should be callable by their symbolical names.Won't help. Consider: a.b.c.d.e.f = g; If anywhere on the path an rvalue is created, the code is useless busywork.
Dec 22 2009
Kagamin wrote:Andrei Alexandrescu Wrote:I think it is to the extent (a) it does nothing (b) the syntactic equivalent code involving fields does something. It's an egregious breakage of consistency because properties were meant to be generalizations of fields in the first place. AndreiIn contrast with the original example this is not a bug.I think, possible solution is to disallow assign operations on the return value: =, +=, *=, ~=, ++ etc, but read operations should be allowed and functions should be callable by their symbolical names.Won't help. Consider: a.b.c.d.e.f = g; If anywhere on the path an rvalue is created, the code is useless busywork.
Dec 22 2009
On Tue, 22 Dec 2009 08:30:33 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Kagamin wrote:What if a.b returns a Rebindable!(const(T)) rvalue, and the rest are lvalues? -SteveAndrei Alexandrescu Wrote:I think it is to the extent (a) it does nothing (b) the syntactic equivalent code involving fields does something. It's an egregious breakage of consistency because properties were meant to be generalizations of fields in the first place.In contrast with the original example this is not a bug.I think, possible solution is to disallow assign operations on the return value: =, +=, *=, ~=, ++ etc, but read operations should be allowed and functions should be callable by their symbolical names.Won't help. Consider: a.b.c.d.e.f = g; If anywhere on the path an rvalue is created, the code is useless busywork.
Dec 22 2009
On Tue, 22 Dec 2009 08:41:11 -0500, Steven Schveighoffer <schveiguy yahoo.com> wrote:On Tue, 22 Dec 2009 08:30:33 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:I just realized in my efforts to use a real phobos-supported wrapper type, I doomed my own argument :) The point is, wrapper types shouldn't have second-class citizenship as rvalues to pointers and references. There's got to be a way to identify types as being lvalues even though the compiler doesn't see it that way. If you can accomplish that, then I have no problem disallowing setting members of true rvalues. -SteveKagamin wrote:What if a.b returns a Rebindable!(const(T)) rvalue, and the rest are lvalues?Andrei Alexandrescu Wrote:I think it is to the extent (a) it does nothing (b) the syntactic equivalent code involving fields does something. It's an egregious breakage of consistency because properties were meant to be generalizations of fields in the first place.In contrast with the original example this is not a bug.I think, possible solution is to disallow assign operations on the return value: =, +=, *=, ~=, ++ etc, but read operations should be allowed and functions should be callable by their symbolical names.Won't help. Consider: a.b.c.d.e.f = g; If anywhere on the path an rvalue is created, the code is useless busywork.
Dec 22 2009
On 2009-12-22 08:45:00 -0500, "Steven Schveighoffer" <schveiguy yahoo.com> said:The point is, wrapper types shouldn't have second-class citizenship as rvalues to pointers and references. There's got to be a way to identify types as being lvalues even though the compiler doesn't see it that way. If you can accomplish that, then I have no problem disallowing setting members of true rvalues.What you need is tail const. Lvalues are similar to tail-const values: can't change them, but can change what they point to. The lvalueness is not transitive, so a transitive const isn't appropriate for representing lvalues. Then of course you need to annotate member functions as being tail-const, and there the const system becomes more complicated. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Dec 22 2009
On Tue, 22 Dec 2009 11:05:52 -0500, Michel Fortin <michel.fortin michelf.com> wrote:On 2009-12-22 08:45:00 -0500, "Steven Schveighoffer" <schveiguy yahoo.com> said:You mean head-const. We have tail-const already (I often get them confused too). But that's not exactly what I mean. Constancy aside, if I have a wrapper struct like this: class C { void setX(int n){...} property S s() {return S(this);} } struct S { private C c; property void x(int n) { c.setX(n); } } I should be able to do this: auto c = new C; c.s.x = 5; Basically, s is giving a restrictive interface to a C, using the power of struct member functions, I can filter what functions are available or what they are named, or log them, or do whatever I want. It's a wrapper type. If s's functions are inlined, then S has almost no overhead, it just provides static interface transformation. Even if there was an annotation like: lvalue struct S { } which hints to the compiler that S can be used as an lvalue, I'd be more than happy to accept Andrei's proposed restriction. -SteveThe point is, wrapper types shouldn't have second-class citizenship as rvalues to pointers and references. There's got to be a way to identify types as being lvalues even though the compiler doesn't see it that way. If you can accomplish that, then I have no problem disallowing setting members of true rvalues.What you need is tail const. Lvalues are similar to tail-const values: can't change them, but can change what they point to. The lvalueness is not transitive, so a transitive const isn't appropriate for representing lvalues. Then of course you need to annotate member functions as being tail-const, and there the const system becomes more complicated.
Dec 22 2009
Andrei Alexandrescu Wrote:class File { ... } struct MyStruct { File obj; File obj2(){ return obj; } } MyStruct foo(); foo().obj.unlink(); foo().obj2().unlink(); Why do you think this is invalid?In contrast with the original example this is not a bug.I think it is to the extent (a) it does nothing (b) the syntactic equivalent code involving fields does something. It's an egregious breakage of consistency because properties were meant to be generalizations of fields in the first place.
Dec 23 2009
Kagamin wrote:Andrei Alexandrescu Wrote:I didn't think that's invalid. I was talking about the other example that looked like a field assignment but did not assign to a field. File does not have an unlink member but I get your point. As I said, refusing to bind ref to rvalues disables a few valid uses. I am willing to renounce those few uses. Andreiclass File { ... } struct MyStruct { File obj; File obj2(){ return obj; } } MyStruct foo(); foo().obj.unlink(); foo().obj2().unlink(); Why do you think this is invalid?In contrast with the original example this is not a bug.I think it is to the extent (a) it does nothing (b) the syntactic equivalent code involving fields does something. It's an egregious breakage of consistency because properties were meant to be generalizations of fields in the first place.
Dec 23 2009
On 2009-12-23 09:15:18 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> said:I didn't think that's invalid. I was talking about the other example that looked like a field assignment but did not assign to a field. File does not have an unlink member but I get your point. As I said, refusing to bind ref to rvalues disables a few valid uses. I am willing to renounce those few uses.Is this one of them? int[5] array = [1, 2, 3, 4, 5]; array[1..3] = [0, 0]; Or, more likely returning a non-built-in range type is one of them: MyContainer container = [1, 2, 3, 4, 5]; container[1..3] = [0, 0]; and you'll have to write it like this: MyContainer container = [1, 2, 3, 4, 5]; MyRange range = container[1..3]; range = [0, 0]; -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Dec 23 2009
Michel Fortin wrote:On 2009-12-23 09:15:18 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> said:That's just mean! :o) Guess I need to acquiesce. Thanks Michel for the example. AndreiI didn't think that's invalid. I was talking about the other example that looked like a field assignment but did not assign to a field. File does not have an unlink member but I get your point. As I said, refusing to bind ref to rvalues disables a few valid uses. I am willing to renounce those few uses.Is this one of them? int[5] array = [1, 2, 3, 4, 5]; array[1..3] = [0, 0]; Or, more likely returning a non-built-in range type is one of them: MyContainer container = [1, 2, 3, 4, 5]; container[1..3] = [0, 0]; and you'll have to write it like this: MyContainer container = [1, 2, 3, 4, 5]; MyRange range = container[1..3]; range = [0, 0];
Dec 23 2009
On 2009-12-23 12:53:54 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> said:Well, if we had a way for saying we want to pass the "this" argument by value, it'd solve the problem nicely. For the example above to work: struct MyRange { void opAssign(...) byvalue; } Only functions with the byvalue attribute (as well as const and immutable functions) could be called on an lvalue. So you could allow assignment and disallow changing the length of the range on an lvalue. -- Michel Fortin michel.fortin michelf.com http://michelf.com/Is this one of them? int[5] array = [1, 2, 3, 4, 5]; array[1..3] = [0, 0]; Or, more likely returning a non-built-in range type is one of them: MyContainer container = [1, 2, 3, 4, 5]; container[1..3] = [0, 0]; and you'll have to write it like this: MyContainer container = [1, 2, 3, 4, 5]; MyRange range = container[1..3]; range = [0, 0];That's just mean! :o) Guess I need to acquiesce. Thanks Michel for the example.
Dec 24 2009
Andrei Alexandrescu Wrote:you'd have to insert explicit variables.Yesterday I hit bug 1893 and I won't apreciate any extra requirement for explicit meaningless temporaries.
Dec 18 2009
Andrei Alexandrescu Wrote:Andrei Alexandrescu Wrote:In contrast with the original example this is not a bug.I think it is to the extent (a) it does nothing (b) the syntactic equivalent code involving fields does something. It's an egregious breakage of consistency because properties were meant to be generalizations of fields in the first place.So you deliberately want to break consistency between fields and properties? As I understnd, .obj will be allowed and .obj2 will be disallowed?foo().obj.unlink(); foo().obj2().unlink();File does not have an unlink member but I get your point. As I said, refusing to bind ref to rvalues disables a few valid uses. I am willing to renounce those few uses.
Dec 25 2009
Kagamin wrote:Andrei Alexandrescu Wrote:Properties that return rvalues cannot be fully consistent with fields anyway. By the change I suggested, false friends that look syntactically the same but do different things are avoided. Anyway, I dropped the suggestion due to the array slice example, but I still think this is a serious problem with D. AndreiAndrei Alexandrescu Wrote:In contrast with the original example this is not a bug.I think it is to the extent (a) it does nothing (b) the syntactic equivalent code involving fields does something. It's an egregious breakage of consistency because properties were meant to be generalizations of fields in the first place.So you deliberately want to break consistency between fields and properties? As I understnd, .obj will be allowed and .obj2 will be disallowed?foo().obj.unlink(); foo().obj2().unlink();File does not have an unlink member but I get your point. As I said, refusing to bind ref to rvalues disables a few valid uses. I am willing to renounce those few uses.
Dec 25 2009
On 2009-12-25 09:13:13 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> said:Anyway, I dropped the suggestion due to the array slice example, but I still think this is a serious problem with D.If you could pass "this" by value instead of ref, it'd solve the problem nicely: struct MyRange { MyRange opSlice(int, int) byvalue; void length(int) property; } Here you can call opSlice on a lvalue since it takes the "this" argument by value, but you can't call length(int) because it takes it by ref. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Dec 25 2009
Michel Fortin wrote:On 2009-12-25 09:13:13 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> said:I've seen that. The only problem I see with that is that it complicates the language. Ideally some sort of inference could be applicable. AndreiAnyway, I dropped the suggestion due to the array slice example, but I still think this is a serious problem with D.If you could pass "this" by value instead of ref, it'd solve the problem nicely: struct MyRange { MyRange opSlice(int, int) byvalue; void length(int) property; } Here you can call opSlice on a lvalue since it takes the "this" argument by value, but you can't call length(int) because it takes it by ref.
Dec 25 2009
On 2009-12-25 11:13:10 -0500, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> said:If we ever get that "universal calling syntax" thing, where any function can be called as if it were a member function, it would be super simple: void MyRange opSlice(MyRange this, int, int) {...} Just don't put "ref" in front of the first argument. -- Michel Fortin michel.fortin michelf.com http://michelf.com/If you could pass "this" by value instead of ref, it'd solve the problem nicely: struct MyRange { MyRange opSlice(int, int) byvalue; void length(int) property; } Here you can call opSlice on a lvalue since it takes the "this" argument by value, but you can't call length(int) because it takes it by ref.I've seen that. The only problem I see with that is that it complicates the language. Ideally some sort of inference could be applicable.
Dec 25 2009