digitalmars.D - Why D const is annoying
- Mehrdad (25/25) Dec 10 2011 I just thought I'd give D another try, after having given up on it for a...
- Mehrdad (14/14) Dec 10 2011 Moar coming... hey look, it's ANOTHER const-ness issue! :(
- Timon Gehr (2/16) Dec 10 2011 http://d.puremagic.com/issues/show_bug.cgi?id=6809
- Timon Gehr (9/35) Dec 10 2011 Just slice the const array to get a range. The specialization for ranges...
- bearophile (4/12) Dec 10 2011 Wasn't arr a range already?
- Timon Gehr (2/14) Dec 10 2011 No, popFront is mutating and const(int[]) cannot be mutated.
- bearophile (4/5) Dec 10 2011 So, is it impossible by design to iterate immutable collections?
- Mehrdad (3/8) Dec 10 2011 I believe the answer is yes (although Timon would probably know better).
- Timon Gehr (6/16) Dec 10 2011 Well, you are essentially complaining about the fact that
- Andrei Alexandrescu (14/24) Dec 10 2011 That is incorrect (it's easy to design a range iterating over an
- Timon Gehr (6/9) Dec 10 2011 No, it is impossible to popFront an immutable array. Because it is,
- Kai Meyer (2/19) Dec 12 2011 Seems to me like popFront isn't the right tool for the job.
- Jonathan M Davis (17/37) Dec 12 2011 ??? That's how you iterate a range. Without it, a range is essentially
- Jonathan M Davis (45/58) Dec 10 2011 Per isInputRange!(typeof(arr)), no. It has the right functions, but it c...
- bearophile (5/11) Dec 10 2011 Thank you Jonathan for taking the time to explain me all that :-)
- Graham St Jack (6/63) Dec 11 2011 Instead of an immutable struct trying to be a range, why not have it
- Mehrdad (4/41) Dec 10 2011 Sure.
- Mehrdad (7/7) Dec 10 2011 ... and another...
- so (2/9) Dec 10 2011 Oh fantastic, so you are writing the first bug-free software of our time...
- Mehrdad (4/16) Dec 10 2011 Nope, sorry. :(
- Timon Gehr (13/20) Dec 10 2011 It is unrelated to const, this gives the same error.
- Mehrdad (13/13) Dec 10 2011 ... and another...
- Timon Gehr (17/30) Dec 10 2011 Again, unrelated to const. This works:
- Walter Bright (4/7) Dec 10 2011 In general, to refer to the current template being expanded from inside ...
- Mehrdad (3/11) Dec 10 2011 That's not the issue.
- Walter Bright (8/22) Dec 10 2011 That's because
- Mehrdad (6/15) Dec 10 2011 Yup, I started using it as soon as Timon mentioned it (thanks for the
- Walter Bright (9/14) Dec 10 2011 It isn't a bug, it is designed to work that way. The type of the instant...
- Timon Gehr (8/25) Dec 10 2011 What is the benefit of having this apparent mis-feature?
- Trass3r (6/16) Dec 10 2011 That error message is crap and struck me as well some time ago.
- Mehrdad (3/8) Dec 10 2011 Uh, what?
- Andrei Alexandrescu (8/25) Dec 10 2011 This is wrong. The type of parameter is size_t, no two ways about it. It...
- Jonathan M Davis (21/57) Dec 10 2011 Templates should _always_ be instantiated on the _value_ of their argume...
- Bernard Helyer (3/26) Dec 10 2011 Madness. I can't believe you think this is sane behaviour.
- Jakob Ovrum (4/7) Dec 10 2011 This. I don't see how it makes any sense from a design
- David Nadlinger (43/50) Dec 10 2011 Which is, in my not-so-humble opinion, just plain broken. Template value...
- Trass3r (1/5) Dec 10 2011 I absolutely agree.
- Walter Bright (3/5) Dec 10 2011 The original idea was that you could add specializations without breakin...
- Andrei Alexandrescu (13/39) Dec 10 2011 Please let's not deflect the issue. If there's a patentable way to annoy...
- SomeDude (2/15) Apr 28 2012 This one now works. :)
- Mehrdad (7/7) Dec 10 2011 ... and another...
- Mehrdad (13/13) Dec 10 2011 ... and another... (yes, this one _IS_ a const issue)
- Walter Bright (9/17) Dec 10 2011 Right. inout has no point if it also does not appear on the return type.
- Mehrdad (4/25) Dec 10 2011 Sure, it's always a design mistake. :-)
- Walter Bright (7/9) Dec 10 2011 Internal to a function, inout behaves like 'const'. You won't be able to...
- Mafi (8/18) Dec 11 2011 But what about:
- Walter Bright (2/8) Dec 11 2011 It should work with const.
- Mafi (13/23) Dec 11 2011 But allowing a variable of type abc(int)* where abc is immutable or
- Andrei Alexandrescu (3/11) Dec 11 2011 immutable(int)* does not convert to immutable(const)*.
- Timon Gehr (4/15) Dec 11 2011 I think you meant an lvalue of type immutable(int)* does not convert to
- Mafi (24/35) Dec 11 2011 [assuming you meant ref const(int)*]
- Jesse Phillips (11/22) Dec 12 2011 Just gave this code a try:
- Timon Gehr (18/40) Dec 13 2011 Interesting. That is another bug that masks the one you were trying to
- Steven Schveighoffer (22/42) Dec 12 2011 That was brought up during discussion on adding the feature. One of the...
- Timon Gehr (13/56) Dec 12 2011 This currently compiles:
- Timon Gehr (4/76) Dec 12 2011 Probably it is better to catch it at the call site. But if that is
- Timon Gehr (16/97) Dec 12 2011 OK, got it.
- Steven Schveighoffer (8/112) Dec 12 2011 I've thought about this for a few minutes, and I can't find a flaw in it...
- Timon Gehr (23/144) Dec 12 2011 For anything that is both input and output, for example:
- Steven Schveighoffer (20/39) Dec 12 2011 This is a great way to think about inout in general:
- Mehrdad (5/26) Dec 10 2011 Oh yeah, I just realized -- I think you missed the _second_ 'inout'.
- Timon Gehr (3/33) Dec 10 2011 Yah, the issue is that *BOTH* your inouts are on a parameter. Just
- Mehrdad (3/5) Dec 10 2011 I don't see what's wrong with that, but yes, that's what I did. And that...
- Timon Gehr (2/7) Dec 10 2011 No, that is not what you did. You did copy the code.
- Steven Schveighoffer (19/49) Dec 11 2011 I'll split it out so it's easier to see:
- Mehrdad (17/17) Dec 10 2011 Oh, nice, here's an ICE for y'all :)
- Mehrdad (5/22) Dec 10 2011 BTW this _DOES_ look like another issue related to const.
- Trass3r (1/1) Dec 10 2011 http://d.puremagic.com/issues/show_bug.cgi?id=7091
- Mehrdad (13/14) Dec 10 2011 Thanks. Sorry I'm too lazy to do that myself lol -- kinda busy studying
- SomeDude (2/3) Apr 28 2012 No more ICE on 2.059 :)
- Walter Bright (3/4) Dec 10 2011 Actually, I think your post is very constructive and worthwhile. Thanks ...
- Mehrdad (3/9) Dec 10 2011 Thanks! Glad to know it's helpful. :) Will post more when I get the
- Mehrdad (8/8) Dec 10 2011 Moar bugs/ICEs...
- SomeDude (2/10) Apr 28 2012 No more ICE on 2.059 Win32
- Mehrdad (22/22) Dec 10 2011 Another bug, this time regarding invariant()....
- SomeDude (2/25) Apr 28 2012 This one is still valid.
- Mehrdad (7/7) Apr 27 2012 Okay, final exams are coming up again, and so are my bugs (I have no ide...
- Mehrdad (3/3) Apr 27 2012 Oh sorry, here's the previous thread... somehow it got detached because ...
- James Miller (21/28) Apr 27 2012 You expected that to work? Extra extra, infinite recursion is
- Mehrdad (13/15) Apr 27 2012 Uhm, why not?
- Max Samukha (14/26) Apr 28 2012 dmd is not smart enough to avoid recursion by treating f as a
- Timon Gehr (5/36) Apr 28 2012 Maybe, but that would be a strange special case.
- Max Samukha (8/49) Apr 28 2012 That is arguable. Non-templated functions are a special
- Timon Gehr (21/56) Apr 28 2012 "Semantic analysis is not done until instantiated."
- Timon Gehr (3/15) Apr 28 2012 D templates are analysed eagerly upon instantiation, whereas C++
- SomeDude (5/24) Apr 28 2012 Having browsed several hundreds bug reports lately, I've seen a
- James Miller (8/27) Apr 28 2012 Furthermore, eager analysis is necessary for other D features
- SomeDude (4/12) Apr 29 2012 Ah, I was wondering about that. Thank you for confirming.
- Mehrdad (3/3) Apr 30 2012 Some ICE for y'all.
- Mehrdad (4/4) Apr 30 2012 Also, what exactly is wrong with this code?
- Timon Gehr (6/10) Apr 30 2012 The current type deduction strategy for IFTI is (imho) too weak.
- Mehrdad (9/13) Apr 30 2012 Is it just "weak", or is it outright wrong?
- Timon Gehr (3/5) Apr 30 2012 That one is a diagnostics bug.
- Mehrdad (9/11) Apr 30 2012 Wha..?!
- Timon Gehr (18/24) May 01 2012 It is not the delegates, it is the type deduction algorithm for implicit...
- Mehrdad (48/55) May 01 2012 I guess the problem is with type deduction, but the trouble is that half...
- Timon Gehr (19/42) May 01 2012 Probably you mean this one:
- Mehrdad (9/9) May 01 2012 Yes, that non-global issue was the exact issue I was referring to. It dr...
- Timon Gehr (3/12) May 02 2012 I agree that it should be fixed, but it is technically not a bug in the
- Robert Clipsham (7/10) May 01 2012 Is this in bugzilla? It can't get fixed if no one knows about it! (Make
- Mehrdad (10/13) May 01 2012 Yeah I just posted it yesterday.
- Mehrdad (6/6) May 01 2012 More problems... similar, but this time related to templates.
- Artur Skawina (4/14) May 02 2012 Because it isn't? Where would 'R' come from in your example?...
- Steven Schveighoffer (4/9) May 02 2012 It's an annoying limitation. IFTI specifically *only* works with
- Mehrdad (25/25) May 01 2012 Let's say you have this hypothetical piece of code:
- Jonathan M Davis (63/94) May 01 2012 Since casting away const and mutating something would break the const sy...
- Mehrdad (26/26) May 01 2012 1. Wouldn't your solution
- Jonathan M Davis (16/44) May 01 2012 D's const does exactly what it intends to do. Yes, that means that lazy
- Chris Cain (17/30) May 01 2012 I've never commented on this issue, but here's my viewpoint:
- Mehrdad (26/36) May 01 2012 Okay, so let's say you're right
- Chris Cain (13/23) May 01 2012 What about the object do you want const? How should this object
- Chris Cain (7/10) May 01 2012 I take the second one back, I've not written any kind of
- Mehrdad (34/42) May 01 2012 I don't think you answered my question.
- Jonathan M Davis (28/41) May 01 2012 What you're thinking about here is logical const. You want a way to indi...
- Mehrdad (4/4) May 01 2012 In the world of OOP, when would "guarantee"ing (so to speak) 'physical'
- Jonathan M Davis (26/31) May 02 2012 No, it's not an implementation detail. When you mark an object as being
- Mehrdad (9/15) May 02 2012 I think you misunderstood my question.
- Jonathan M Davis (39/58) May 02 2012 Because foo must be const, or if can't be called on a const object. And ...
- Mehrdad (5/5) May 02 2012 Yes, 'const' is part of the interface.
- Michel Fortin (10/17) May 02 2012 When you're making the object 'const', you're not making the assumption
- Mehrdad (3/9) May 02 2012 So you're saying the same thing: Derived classes CANNOT have mutable
- Jacob Carlborg (23/32) May 02 2012 No:
- Mehrdad (3/3) May 02 2012 Er, I guess I didn't say what I actually meant to say, my bad. x_x
- Timon Gehr (2/3) May 02 2012 =O
- Mehrdad (3/5) May 02 2012 a*
- Jacob Carlborg (6/9) May 02 2012 Yes, that would be the assumption. It's not possible without subverting
- Timon Gehr (16/22) May 02 2012 interface Readonly{
- Chris Cain (91/128) May 02 2012 If you want just to specify that (and not ask the compiler to
- Jacob Carlborg (5/10) May 02 2012 For D, it's not limited to the public interface. The rules apply to ALL
- Chris Cain (7/9) May 02 2012 Indeed. I may have forgot to add "in any way shape form or
- Mehrdad (24/37) May 02 2012 Okay...
- Chris Cain (89/106) May 02 2012 Indeed, const/immutable makes multithreading much better. It
- Mehrdad (5/5) May 02 2012 Okay thanks for the replies, that clears up a bunch of things for me.
- Chris Cain (16/23) May 02 2012 You can set interface methods to be const, but you have to
- Jonathan M Davis (13/20) May 02 2012 You _can_ make the interface methods const if you're willing to deal wit...
- Mehrdad (3/3) May 02 2012 Could someone mention a case where it is __necessary__ to cast away cons...
- =?UTF-8?B?QWxleCBSw7hubmUgUGV0ZXJzZW4=?= (6/10) May 02 2012 shared? Almost always in any non-trivial application. shared is only
- David Nadlinger (5/9) May 02 2012 Additionally, shared is currently little more than a marker for
-
Steven Schveighoffer
(22/28)
May 03 2012
On Wed, 02 May 2012 12:59:34 -0400, David Nadlinger
... - David Nadlinger (10/25) May 03 2012 Yes, it is important indeed, at least if we want the type system
- Oleg Kuporosov (21/21) May 04 2012 Hi folks,
- Mehrdad (2/2) May 02 2012 Okay, that's for shared.
- Jonathan M Davis (28/39) May 01 2012 Yes. And if you mark it as const, then the compiler will guarantee it. B...
- Mehrdad (1/4) May 01 2012 Maybe I'm being stupid, but how is the case any different in D?
- Jonathan M Davis (24/29) May 01 2012 D's type system assumes that const cannot be altered. As such, the compi...
- Mehrdad (13/13) May 01 2012 You said:
- Mehrdad (1/2) May 01 2012 Typo, I meant "DOES prevent"
- Jonathan M Davis (30/41) May 02 2012 No, what the compiler guarantees is _very_ relevant to using const for
- Mehrdad (8/8) May 02 2012 Whoa, what?
- Jonathan M Davis (22/33) May 02 2012 I don't follow.
- Mehrdad (25/46) May 02 2012 You can replace "signed integer overflow" with pretty much whatever
- Jonathan M Davis (13/77) May 02 2012 All of that might hold if the compiler could actually make optimizations...
- Timon Gehr (2/5) May 02 2012 x+1 > x ==> true
- Mehrdad (2/5) May 02 2012 Look for "signed integer overflow" here.
- Andrei Alexandrescu (3/11) May 02 2012 Casting away const should be statically disallowed in @safe code.
- H. S. Teoh (7/8) May 02 2012 [...]
- bearophile (12/13) May 02 2012 Right, this:
- H. S. Teoh (18/34) May 02 2012 [...]
- bearophile (7/10) May 02 2012 There was a long discussion about this, with proposals like
- SomeDude (12/19) May 02 2012 Yes, pure functional languages do provide this kind of guarantee,
- bearophile (7/13) May 02 2012 Right.
- Mehrdad (9/9) May 01 2012 Also, I think you didn't notice the other problem.
- Steven Schveighoffer (22/31) May 02 2012 There are two solutions, both are horrible.
- Mehrdad (10/28) May 02 2012 That's what scares me lol
- Steven Schveighoffer (5/12) May 02 2012 This is a false choice. The performance of Java strings (BTW,
- bearophile (6/10) May 01 2012 Casting away casts is rather dangerous in D (more than in C++), I
I just thought I'd give D another try, after having given up on it for a while. Lo and behold... the same old kind of problem from a year ago is still here. :( Simple stuff like this: import std.algorithm; void main() { const arr = [1, 2, 3]; reduce!"a*b"(arr); // You'd think it'd work... } Results in ridiculously annoying errors like: // algorithm.d(728): Error: can only initialize const member _field_field_0 inside constructor I **HIGHLY** suggest that priority be given to simple problems like these, instead of OMG-so-cool-libraries. It really doesn't matter if there is an uber-awesome collections/CURL/whatever library out there, when problems like these exist. If a 2-line piece of code needs a workaround, then (IMO) people simply won't care about anything else that's more complicated. I'll post more as I find them. (I'd found plenty a few months ago, now I just need to find them again.) Too lazy/busy studying to post as a bug. And it feels a bit better when I add the complaint. :P (Sorry for being so critical but at least I tried to make it constructive...)
Dec 10 2011
Moar coming... hey look, it's ANOTHER const-ness issue! :( import std.algorithm; struct Matrix { size_t[] _lengths; property inout(size_t)[] lengths() inout { return _lengths; } property size_t length() inout { return reduce!q{a*b}(lengths); } } // Error: template std.algorithm.reduce!("a*b").reduce(Args...) if (Args.length > 0 && Args.length <= 2 && isIterable!(Args[__dollar - 1])) does not match any function template declaration // Error: template std.algorithm.reduce!("a*b").reduce(Args...) if (Args.length > 0 && Args.length <= 2 && isIterable!(Args[__dollar - 1])) cannot deduce template function from argument types !()(inout(uint[2u]))
Dec 10 2011
On 12/10/2011 10:43 AM, Mehrdad wrote:Moar coming... hey look, it's ANOTHER const-ness issue! :( import std.algorithm; struct Matrix { size_t[] _lengths; property inout(size_t)[] lengths() inout { return _lengths; } property size_t length() inout { return reduce!q{a*b}(lengths); } } // Error: template std.algorithm.reduce!("a*b").reduce(Args...) if (Args.length > 0 && Args.length <= 2 && isIterable!(Args[__dollar - 1])) does not match any function template declaration // Error: template std.algorithm.reduce!("a*b").reduce(Args...) if (Args.length > 0 && Args.length <= 2 && isIterable!(Args[__dollar - 1])) cannot deduce template function from argument types !()(inout(uint[2u]))http://d.puremagic.com/issues/show_bug.cgi?id=6809
Dec 10 2011
On Saturday, 10 December 2011 at 09:50:27 UTC, Timon Gehr wrote:On 12/10/2011 10:43 AM, Mehrdad wrote: http://d.puremagic.com/issues/show_bug.cgi?id=6809This one now works (I just updated the issue), but the bug with reduce! is still open.
Apr 28 2012
On Saturday, 28 April 2012 at 09:26:36 UTC, SomeDude wrote:On Saturday, 10 December 2011 at 09:50:27 UTC, Timon Gehr wrote:Never mind. I just read the answer from Jonathan Davis. Still, this should be documented somewhere, because the fact that you have to slice a const array is not immediately obvious.On 12/10/2011 10:43 AM, Mehrdad wrote: http://d.puremagic.com/issues/show_bug.cgi?id=6809This one now works (I just updated the issue), but the bug with reduce! is still open.
Apr 28 2012
On 12/10/2011 10:37 AM, Mehrdad wrote:I just thought I'd give D another try, after having given up on it for a while. Lo and behold... the same old kind of problem from a year ago is still here. :( Simple stuff like this: import std.algorithm; void main() { const arr = [1, 2, 3]; reduce!"a*b"(arr); // You'd think it'd work... } Results in ridiculously annoying errors like: // algorithm.d(728): Error: can only initialize const member _field_field_0 inside constructor I **HIGHLY** suggest that priority be given to simple problems like these, instead of OMG-so-cool-libraries.It is a *library bug*. Just have a look at the source code.It really doesn't matter if there is an uber-awesome collections/CURL/whatever library out there, when problems like these exist. If a 2-line piece of code needs a workaround, then (IMO) people simply won't care about anything else that's more complicated. I'll post more as I find them. (I'd found plenty a few months ago, now I just need to find them again.) Too lazy/busy studying to post as a bug. And it feels a bit better when I add the complaint. :P (Sorry for being so critical but at least I tried to make it constructive...)Just slice the const array to get a range. The specialization for ranges does not have the bug. import std.algorithm; void main() { const arr = [1, 2, 3]; reduce!"a*b"(arr[]); // It works. }
Dec 10 2011
Timon Gehr:Just slice the const array to get a range. The specialization for ranges does not have the bug. import std.algorithm; void main() { const arr = [1, 2, 3]; reduce!"a*b"(arr[]); // It works. }Wasn't arr a range already? Bye, bearophile
Dec 10 2011
On 12/10/2011 11:45 AM, bearophile wrote:Timon Gehr:No, popFront is mutating and const(int[]) cannot be mutated.Just slice the const array to get a range. The specialization for ranges does not have the bug. import std.algorithm; void main() { const arr = [1, 2, 3]; reduce!"a*b"(arr[]); // It works. }Wasn't arr a range already? Bye, bearophile
Dec 10 2011
Timon Gehr:No, popFront is mutating and const(int[]) cannot be mutated.So, is it impossible by design to iterate immutable collections? Bye, bearophile
Dec 10 2011
On 12/10/2011 3:08 AM, bearophile wrote:Timon Gehr:I believe the answer is yes (although Timon would probably know better). That's one reason I believe const is broken...No, popFront is mutating and const(int[]) cannot be mutated.So, is it impossible by design to iterate immutable collections? Bye, bearophile
Dec 10 2011
On 12/10/2011 12:10 PM, Mehrdad wrote:On 12/10/2011 3:08 AM, bearophile wrote:You can always have a mutable range over the immutable collection contents.Timon Gehr:I believe the answer is yes (although Timon would probably know better).No, popFront is mutating and const(int[]) cannot be mutated.So, is it impossible by design to iterate immutable collections? Bye, bearophileThat's one reason I believe const is broken...Well, you are essentially complaining about the fact that const/immutable variables cannot be changed. I believe that very likely this is the point of having them. If you want to mutate a variable don't make its type const or immutable.
Dec 10 2011
On 12/10/11 5:10 AM, Mehrdad wrote:On 12/10/2011 3:08 AM, bearophile wrote:That is incorrect (it's easy to design a range iterating over an immutable collection, and in particular const(T)[] iterates a const T[] no problem), but taking the time to vent about the annoyances you've encountered is highly appreciated. I suspect many people try D just like you do, with a few cool ideas (no conservative "hello world" programs), see they don't work for obscure reasons, go like "meh", and give up. So having such an experience _documented_ is of great value. I'm every bit as annoyed as you are about simple things not working with qualifiers, and I have a few ideas on how to fix it. I should add that I very strongly believe a problem with a workaround does not implicitly cease being a problem. AndreiTimon Gehr:I believe the answer is yes (although Timon would probably know better). That's one reason I believe const is broken...No, popFront is mutating and const(int[]) cannot be mutated.So, is it impossible by design to iterate immutable collections? Bye, bearophile
Dec 10 2011
On 12/10/2011 12:08 PM, bearophile wrote:Timon Gehr:No, it is impossible to popFront an immutable array. Because it is, well, immutable. But you can always slice it and get a range back that can be used to iterate the array (btw thanks to the devs for fixing that, that did not work a few releases ago!). Library defined immutable collection types can/should use the same pattern.No, popFront is mutating and const(int[]) cannot be mutated.So, is it impossible by design to iterate immutable collections?
Dec 10 2011
On 12/10/2011 03:52 AM, Timon Gehr wrote:On 12/10/2011 11:45 AM, bearophile wrote:Seems to me like popFront isn't the right tool for the job.Timon Gehr:No, popFront is mutating and const(int[]) cannot be mutated.Just slice the const array to get a range. The specialization for ranges does not have the bug. import std.algorithm; void main() { const arr = [1, 2, 3]; reduce!"a*b"(arr[]); // It works. }Wasn't arr a range already? Bye, bearophile
Dec 12 2011
On Monday, December 12, 2011 12:13:54 Kai Meyer wrote:On 12/10/2011 03:52 AM, Timon Gehr wrote:??? That's how you iterate a range. Without it, a range is essentially useless. There _is_ no other tool for the job. Ranges _could_ have been designed more like slists and use head and tail and avoid needing to be mutated, but that's less efficient, since you have to copy the range every time that you iterate to the next item. Generally, the way to solve the issue of a const or immutable range is to get a tail-const copy of it. With arrays, that's easy - just slice it. With user- defined ranges, it requires that opSlice be defined that way, which it may or may not be. But you can't iterate over a const range anymore than you can iterate over a const iterator in C++. In both cases, you need a non-const copy if you want to iterate. There's nothing odd about it really. It's just that templates take the _exact_ type with IFTI and so don't generally work with const or immutable ranges. The proposed change to make arrays instantiate templates with their tail-const type with IFTI will fix that for arrays though. - Jonathan M DavisOn 12/10/2011 11:45 AM, bearophile wrote:Seems to me like popFront isn't the right tool for the job.Timon Gehr:No, popFront is mutating and const(int[]) cannot be mutated.Just slice the const array to get a range. The specialization for ranges does not have the bug. import std.algorithm; void main() { const arr = [1, 2, 3]; reduce!"a*b"(arr[]); // It works. }Wasn't arr a range already? Bye, bearophile
Dec 12 2011
On Saturday, December 10, 2011 05:45:11 bearophile wrote:Timon Gehr:Per isInputRange!(typeof(arr)), no. It has the right functions, but it can't use them, because it's const. A const range is essentially useless, because you can't iterate over it. When a template is instantiated, it's instantiated on the exact types that it's given. So, if you given a const or immutable array, it's going to instantiate on that type, even though it _could_ theoretically instantiate itself with a mutable array with const or immutable elements. And since, a const or immutable array won't work as a range, the template instantiation fails. The range-based functions in std.array and std.string work with const and immutable arrays simply because they are either coded specifically for arrays or have specializations for them. You need a function which takes const(T)[] rather than one which takes const T[]. std.array and std.string typically take const(T)[] rather than const T[], whereas a more general range function is taking R, which in the case above is determined to be const int[], which won't work as a range. It used to be that the slice of an array was the exact type of that array, meaning Timon's solution wouldn't work without a cast, because the slice would be just as const as the original. Fortunately, that has been changed, so now a slice of a const or immutable array will have its elements be const or immutable but not the slice itself. So, now const and immutable arrays are like static arrays in that you need to slice them to use them with range-based functions, but you can't use them directly with range-based functions. A bigger problem is const or immutable ranges which are structs. Unless the programmer who defined the range type managed to have an opSlice which returned a version with const or immutable elements but where the range itself wasn't const or immutable (i.e. a tail-const slice), then you can't even get slicing to work. And even if templates were improved to the point that they would instantiate const int[] as const(int)[] (which I expect is a change which will never happen due to the difficulties in doing so), that wouldn't solve the problem for ranges which aren't arrays. Also, const and immutable ranges which aren't arrays which have no opSlice will _never_ work, because there's no way to get a mutable slice of them to operate on, so you're stuck with a const or immutable range. Really what this means is that you need to slice const and immutable ranges when you pass them to range-based functions, and then as long as they're arrays or their opSlices have been defined properly, it'll work just fine. If ranges had been designed more like slists (i.e. they have head and tail rather than front and popFront), then they would have been inherently tail- const and we wouldn't be having these problems. But that's less efficent, because you have to copy the range every time that you want to remove an element from it. Regardless, it's too late now. Ranges are too heavily used with their current API for us to change it that drastically now. - Jonathan M DavisJust slice the const array to get a range. The specialization for ranges does not have the bug. import std.algorithm; void main() { const arr = [1, 2, 3]; reduce!"a*b"(arr[]); // It works. }Wasn't arr a range already?
Dec 10 2011
Jonathan M Davis:Per isInputRange!(typeof(arr)), no. It has the right functions, but it can't use them, because it's const. [... snip] Ranges are too heavily used with their current API for us to change it that drastically now.Thank you Jonathan for taking the time to explain me all that :-) I am taking years to learn D well :-) Bye, bearophile
Dec 10 2011
On 11/12/11 06:55, Jonathan M Davis wrote:On Saturday, December 10, 2011 05:45:11 bearophile wrote:Instead of an immutable struct trying to be a range, why not have it return a mutable range over its immutable elements? This would be analogous to slicing an immutable array.Timon Gehr:Per isInputRange!(typeof(arr)), no. It has the right functions, but it can't use them, because it's const. A const range is essentially useless, because you can't iterate over it. When a template is instantiated, it's instantiated on the exact types that it's given. So, if you given a const or immutable array, it's going to instantiate on that type, even though it _could_ theoretically instantiate itself with a mutable array with const or immutable elements. And since, a const or immutable array won't work as a range, the template instantiation fails. The range-based functions in std.array and std.string work with const and immutable arrays simply because they are either coded specifically for arrays or have specializations for them. You need a function which takes const(T)[] rather than one which takes const T[]. std.array and std.string typically take const(T)[] rather than const T[], whereas a more general range function is taking R, which in the case above is determined to be const int[], which won't work as a range. It used to be that the slice of an array was the exact type of that array, meaning Timon's solution wouldn't work without a cast, because the slice would be just as const as the original. Fortunately, that has been changed, so now a slice of a const or immutable array will have its elements be const or immutable but not the slice itself. So, now const and immutable arrays are like static arrays in that you need to slice them to use them with range-based functions, but you can't use them directly with range-based functions. A bigger problem is const or immutable ranges which are structs. Unless the programmer who defined the range type managed to have an opSlice which returned a version with const or immutable elements but where the range itself wasn't const or immutable (i.e. a tail-const slice), then you can't even get slicing to work. And even if templates were improved to the point that they would instantiate const int[] as const(int)[] (which I expect is a change which will never happen due to the difficulties in doing so), that wouldn't solve the problem for ranges which aren't arrays.Just slice the const array to get a range. The specialization for ranges does not have the bug. import std.algorithm; void main() { const arr = [1, 2, 3]; reduce!"a*b"(arr[]); // It works. }Wasn't arr a range already?Also, const and immutable ranges which aren't arrays which have no opSlice will _never_ work, because there's no way to get a mutable slice of them to operate on, so you're stuck with a const or immutable range. Really what this means is that you need to slice const and immutable ranges when you pass them to range-based functions, and then as long as they're arrays or their opSlices have been defined properly, it'll work just fine. If ranges had been designed more like slists (i.e. they have head and tail rather than front and popFront), then they would have been inherently tail- const and we wouldn't be having these problems. But that's less efficent, because you have to copy the range every time that you want to remove an element from it. Regardless, it's too late now. Ranges are too heavily used with their current API for us to change it that drastically now. - Jonathan M Davis-- Graham St Jack
Dec 11 2011
On 12/10/2011 2:00 AM, Timon Gehr wrote:On 12/10/2011 10:37 AM, Mehrdad wrote:Sure. Did I say something else?I just thought I'd give D another try, after having given up on it for a while. Lo and behold... the same old kind of problem from a year ago is still here. :( Simple stuff like this: import std.algorithm; void main() { const arr = [1, 2, 3]; reduce!"a*b"(arr); // You'd think it'd work... } Results in ridiculously annoying errors like: // algorithm.d(728): Error: can only initialize const member _field_field_0 inside constructor I **HIGHLY** suggest that priority be given to simple problems like these, instead of OMG-so-cool-libraries.It is a *library bug*. Just have a look at the source code.Thanks for the workaround, but it's still a workaround, not a fix. :PIt really doesn't matter if there is an uber-awesome collections/CURL/whatever library out there, when problems like these exist. If a 2-line piece of code needs a workaround, then (IMO) people simply won't care about anything else that's more complicated. I'll post more as I find them. (I'd found plenty a few months ago, now I just need to find them again.) Too lazy/busy studying to post as a bug. And it feels a bit better when I add the complaint. :P (Sorry for being so critical but at least I tried to make it constructive...)Just slice the const array to get a range. The specialization for ranges does not have the bug.
Dec 10 2011
... and another... struct S(T, int N) { public auto opBinary(string op)(in S!(T, N) other) const { return this; } } void main() { S!(int, 1) m; m = m * m; } Error: 'm' is not of arithmetic type, it is a Matrix!(int,1) Error: 'm' is not of arithmetic type, it is a Matrix!(int,1)
Dec 10 2011
On Sat, 10 Dec 2011 12:18:31 +0200, Mehrdad <wfunction hotmail.com> wrote:... and another... struct S(T, int N) { public auto opBinary(string op)(in S!(T, N) other) const { return this; } } void main() { S!(int, 1) m; m = m * m; } Error: 'm' is not of arithmetic type, it is a Matrix!(int,1) Error: 'm' is not of arithmetic type, it is a Matrix!(int,1)Oh fantastic, so you are writing the first bug-free software of our time?
Dec 10 2011
On 12/10/2011 2:28 AM, so wrote:On Sat, 10 Dec 2011 12:18:31 +0200, Mehrdad <wfunction hotmail.com> wrote:Nope, sorry. :( I'm listing a bunch of bugs I've found. And yes, I'm [semi-]complaining about them in the process.... and another... struct S(T, int N) { public auto opBinary(string op)(in S!(T, N) other) const { return this; } } void main() { S!(int, 1) m; m = m * m; } Error: 'm' is not of arithmetic type, it is a Matrix!(int,1) Error: 'm' is not of arithmetic type, it is a Matrix!(int,1)Oh fantastic, so you are writing the first bug-free software of our time?
Dec 10 2011
On 12/10/2011 11:18 AM, Mehrdad wrote:... and another... struct S(T, int N) { public auto opBinary(string op)(in S!(T, N) other) const { return this; } } void main() { S!(int, 1) m; m = m * m; } Error: 'm' is not of arithmetic type, it is a Matrix!(int,1) Error: 'm' is not of arithmetic type, it is a Matrix!(int,1)It is unrelated to const, this gives the same error. struct S(T, int N) { public auto opBinary(string op)(S!(T, N) other) { return this; } } void main() { S!(int, 1) m; m = m * m; } And this works: struct S(T, int N) { public auto opBinary(string op)(in S other) const { return this; } } void main() { S!(int, 1) m; m = m * m; } You have to make sure that the signature of your template actually compiles. Maybe the compiler should emit a diagnostic for that case. If you blame all your compile errors on const, I can understand why you think its design is broken.
Dec 10 2011
On 12/10/2011 2:46 AM, Timon Gehr wrote:On 12/10/2011 11:18 AM, Mehrdad wrote:Yeah, I've complained about const before, but I never said this one was related. :P I just had the word 'const' in there because it was in my original code and I forgot to take it out.... and another... struct S(T, int N) { public auto opBinary(string op)(in S!(T, N) other) const { return this; } } void main() { S!(int, 1) m; m = m * m; } Error: 'm' is not of arithmetic type, it is a Matrix!(int,1) Error: 'm' is not of arithmetic type, it is a Matrix!(int,1)It is unrelated to const, this gives the same error. struct S(T, int N) { public auto opBinary(string op)(S!(T, N) other) { return this; } } void main() { S!(int, 1) m; m = m * m; }And this works: struct S(T, int N) { public auto opBinary(string op)(in S other) const { return this; } } void main() { S!(int, 1) m; m = m * m; } You have to make sure that the signature of your template actually compiles. Maybe the compiler should emit a diagnostic for that case.Sure, but what's wrong with my version that makes it not compile?If you blame all your compile errors on const, I can understand why you think its design is broken.Not _all_ of them (e.g. I figured this one wasn't one of them), but yes, a significant fraction of them that I've seen, only a few of which I've been in the mood to post. :) So yeah, that's why I've come to the conclusion that it's broken.
Dec 10 2011
Oh right, I see why you assumed I blamed it on const -- it's in the title of the post. Nah, only the beginning was about const. The rest was the issues I saw when working on the same library, so I posted them here. Sorry for keeping the (now-misleading) title.
Dec 10 2011
... and another... struct Matrix(T, size_t N = 1) { public auto mul(Matrix!(T, N) other) const { return this; } } void main() { Matrix!(int, 2) m; m.mul(m); } annoy.d(7): Error: function annoy.Matrix!(int,2).Matrix.mul (Matrix!(int,N) other) const is not callable using argument types (Matrix!(int,2)) annoy.d(7): Error: cannot implicitly convert expression (m) of type Matrix!(int,2) to Matrix!(int,N)
Dec 10 2011
On 12/10/2011 11:34 AM, Mehrdad wrote:... and another... struct Matrix(T, size_t N = 1) { public auto mul(Matrix!(T, N) other) const { return this; } } void main() { Matrix!(int, 2) m; m.mul(m); } annoy.d(7): Error: function annoy.Matrix!(int,2).Matrix.mul (Matrix!(int,N) other) const is not callable using argument types (Matrix!(int,2)) annoy.d(7): Error: cannot implicitly convert expression (m) of type Matrix!(int,2) to Matrix!(int,N)Again, unrelated to const. This works: struct Matrix(T, size_t N = 1) { public auto mul(Matrix other) const { return this; } } void main() { Matrix!(int, 2) m; m.mul(m); } But it seems to be a bug indeed, because this compiles: struct Matrix(T, size_t N = 1) { public auto mul(Matrix!(T, 2) other) const { return this; } } void main() { Matrix!(int, 2) m; m.mul(m); }
Dec 10 2011
On 12/10/2011 2:34 AM, Mehrdad wrote:... and another... struct Matrix(T, size_t N = 1) { public auto mul(Matrix!(T, N) other) const { return this; } }In general, to refer to the current template being expanded from inside of it, just give its name, as in: Matrix!(T,N) => Matrix
Dec 10 2011
On 12/10/2011 3:01 AM, Walter Bright wrote:On 12/10/2011 2:34 AM, Mehrdad wrote:That's not the issue. If you say 'int' instead of size_t, it works fine.... and another... struct Matrix(T, size_t N = 1) { public auto mul(Matrix!(T, N) other) const { return this; } }In general, to refer to the current template being expanded from inside of it, just give its name, as in: Matrix!(T,N) => Matrix
Dec 10 2011
On 12/10/2011 3:04 AM, Mehrdad wrote:On 12/10/2011 3:01 AM, Walter Bright wrote:That's because Matrix!(int, cast(int)1) is considered a different template instantiation (and hence a different type) from Matrix!(int, cast(uint)1) Instantiation types are based on the arguments' types, not the parameters' types. If you use my suggestion for the shorthand notation, your code will work as you expect.On 12/10/2011 2:34 AM, Mehrdad wrote:That's not the issue. If you say 'int' instead of size_t, it works fine.... and another... struct Matrix(T, size_t N = 1) { public auto mul(Matrix!(T, N) other) const { return this; } }In general, to refer to the current template being expanded from inside of it, just give its name, as in: Matrix!(T,N) => Matrix
Dec 10 2011
On 12/10/2011 3:11 AM, Walter Bright wrote:That's because Matrix!(int, cast(int)1) is considered a different template instantiation (and hence a different type) from Matrix!(int, cast(uint)1) Instantiation types are based on the arguments' types, not the parameters' types. If you use my suggestion for the shorthand notation, your code will work as you expect.Yup, I started using it as soon as Timon mentioned it (thanks for the suggestion!). But I was referring to the bug, not to the workaround. :) (I don't understand why Matrix!(int, cast(int)1) is considered a different instantiation, when it can't even be instantiated...)
Dec 10 2011
On 12/10/2011 3:17 AM, Mehrdad wrote:Yup, I started using it as soon as Timon mentioned it (thanks for the suggestion!). But I was referring to the bug, not to the workaround. :)It isn't a bug, it is designed to work that way. The type of the instantiation is based on the argument types, not the parameter types. (Note that "2" is the argument and "N" is the parameter.) Hence the message: Error: cannot implicitly convert expression (m) of type Matrix!(int,2) to Matrix!(int,N)(I don't understand why Matrix!(int, cast(int)1) is considered a different instantiation, when it can't even be instantiated...)Yes, it can be instantiated. But it cannot be implicitly converted to type Matrix!(int, cast(uint)1) because they are different types.
Dec 10 2011
On 12/10/2011 12:27 PM, Walter Bright wrote:On 12/10/2011 3:17 AM, Mehrdad wrote:I think the design might contain a bug and should be revisited.Yup, I started using it as soon as Timon mentioned it (thanks for the suggestion!). But I was referring to the bug, not to the workaround. :)It isn't a bug, it is designed to work that way.The type of the instantiation is based on the argument types, not the parameter types. (Note that "2" is the argument and "N" is the parameter.) Hence the message: Error: cannot implicitly convert expression (m) of type Matrix!(int,2) to Matrix!(int,N)What is the benefit of having this apparent mis-feature? It is similar to having the calling conventions of a function depend on the actual arguments instead of the formal parameters. It would likely result in a segmentation fault. For templates, luckily the compiler only blows up with an error message in counter-intuitive ways. I think there must be a very good reason to keep this.(I don't understand why Matrix!(int, cast(int)1) is considered a different instantiation, when it can't even be instantiated...)Yes, it can be instantiated. But it cannot be implicitly converted to type Matrix!(int, cast(uint)1) because they are different types.
Dec 10 2011
Am 10.12.2011, 12:27 Uhr, schrieb Walter Bright <newshound2 digitalmars.com>:On 12/10/2011 3:17 AM, Mehrdad wrote:That error message is crap and struck me as well some time ago. It hides the real cause: int != size_t 1 is implicitly convertible to size_t so what's the problem with using this conversion if no explicit template specialization with int exists?Yup, I started using it as soon as Timon mentioned it (thanks for the suggestion!). But I was referring to the bug, not to the workaround. :)It isn't a bug, it is designed to work that way. The type of the instantiation is based on the argument types, not the parameter types. (Note that "2" is the argument and "N" is the parameter.) Hence the message: Error: cannot implicitly convert expression (m) of type Matrix!(int,2) to Matrix!(int,N)
Dec 10 2011
On 12/10/2011 3:27 AM, Walter Bright wrote:Uh, what? How can it be instantiated? There's no such template in my code...(I don't understand why Matrix!(int, cast(int)1) is considered a different instantiation, when it can't even be instantiated...)Yes, it can be instantiated.
Dec 10 2011
On 12/10/11 5:27 AM, Walter Bright wrote:On 12/10/2011 3:17 AM, Mehrdad wrote:The design is wrong.Yup, I started using it as soon as Timon mentioned it (thanks for the suggestion!). But I was referring to the bug, not to the workaround. :)It isn't a bug, it is designed to work that way.The type of the instantiation is based on the argument types, not the parameter types.This is wrong. The type of parameter is size_t, no two ways about it. It is NOT alias.(Note that "2" is the argument and "N" is the parameter.)Not that N has type size_t.Hence the message: Error: cannot implicitly convert expression (m) of type Matrix!(int,2) to Matrix!(int,N)The message reflects a mistake.They shouldn't. Andrei(I don't understand why Matrix!(int, cast(int)1) is considered a different instantiation, when it can't even be instantiated...)Yes, it can be instantiated. But it cannot be implicitly converted to type Matrix!(int, cast(uint)1) because they are different types.
Dec 10 2011
On Saturday, December 10, 2011 13:12:06 Andrei Alexandrescu wrote:On 12/10/11 5:27 AM, Walter Bright wrote:Templates should _always_ be instantiated on the _value_ of their arguments. In the case such as template t(T) {} t!int the template argument is a type, so the template is instantiated on that type. In the case of template t(int i) {} t!5 the argument is the value 5, so the template should be instatiated on the value 5, regardless of whether the argument is an int or uint or any other type implicitly convertible to the template's parameter's type. I do not understand how Walter could think otherwise. Having template t(int i) {} t!5 t!5u create two separate templates makes no sense. I don't see what value it could possible have. And with the addition of CTFE functions or other templates whose result is auto, it could cause real problems. Bug for this: http://d.puremagic.com/issues/show_bug.cgi?id=3467 - Jonathan M DavisOn 12/10/2011 3:17 AM, Mehrdad wrote:The design is wrong.Yup, I started using it as soon as Timon mentioned it (thanks for the suggestion!). But I was referring to the bug, not to the workaround. :)It isn't a bug, it is designed to work that way.The type of the instantiation is based on the argument types, not the parameter types.This is wrong. The type of parameter is size_t, no two ways about it. It is NOT alias.(Note that "2" is the argument and "N" is the parameter.)Not that N has type size_t.Hence the message: Error: cannot implicitly convert expression (m) of type Matrix!(int,2) to Matrix!(int,N)The message reflects a mistake.They shouldn't.(I don't understand why Matrix!(int, cast(int)1) is considered a different instantiation, when it can't even be instantiated...)Yes, it can be instantiated. But it cannot be implicitly converted to type Matrix!(int, cast(uint)1) because they are different types.
Dec 10 2011
On Saturday, 10 December 2011 at 11:11:44 UTC, Walter Bright wrote:On 12/10/2011 3:04 AM, Mehrdad wrote:Madness. I can't believe you think this is sane behaviour.On 12/10/2011 3:01 AM, Walter Bright wrote:That's because Matrix!(int, cast(int)1) is considered a different template instantiation (and hence a different type) from Matrix!(int, cast(uint)1)On 12/10/2011 2:34 AM, Mehrdad wrote:That's not the issue. If you say 'int' instead of size_t, it works fine.... and another... struct Matrix(T, size_t N = 1) { public auto mul(Matrix!(T, N) other) const { return this; } }In general, to refer to the current template being expanded from inside of it, just give its name, as in: Matrix!(T,N) => Matrix
Dec 10 2011
On Saturday, 10 December 2011 at 12:15:14 UTC, David Nadlinger wrote:So, could you please at least elaborate why you think the current behavior is correct, and what advantages it has to outweigh the massive confusion it causes?This. I don't see how it makes any sense from a design perspective.
Dec 10 2011
On 12/10/11 12:11 PM, Walter Bright wrote:That's because Matrix!(int, cast(int)1) is considered a different template instantiation (and hence a different type) from Matrix!(int, cast(uint)1) Instantiation types are based on the arguments' types, not the parameters' types.Which is, in my not-so-humble opinion, just plain broken. Template value parameters are, well, values, and there shouldn't ever be a difference between whether a value of an integral type represents 1 or 1u (if it accepts both). This is a long-standing bug, and makes unsigned template value parameters and, to a lesser extent, integral template value parameters in general virtually unusable, because you hit hard to debug issues with confusing error messages all over the place. It gets even worse once CTFE comes into play (or compile-time values in general, as opposed to directly passing number literals) for two reasons: First, the error messages become totally useless, i.e. in most cases something similar to »Foo!(n) can't be implicitly converted to Foo!(n)«. Second, even if you litter your code with explicit casts all over the place, it is not always possible to get it to behave like intended because of what I presume are issues with constant folding. I know this first hand, because I worked on a units-of-measurement library which relies on units being uniquely represented as types, because the unit type becomes part of the type of quantities as a template parameter (I presented it at the NG a while back, by the way: http://www.digitalmars.com/d/archives/digitalmars/D/RFC_Units_of_measurement_for_D_ hobos_134590.html). For derived units (e.g. kg m/s^2), you need to »normalize« the exponents using compile time rational arithmetic, which was really hard to get to work because of the issue discussed here (I don't even remember all the details). But even with my own unit tests passing, I now once in a while get a message saying something like: »Hey, I checked out your units library prototype, and while it works nice for most cases, I encountered this situation where … [some situation in which 1 != 1 for DMD]«. I tried hard to work around this, but I couldn't find a reliable solution. Also, I'd like to note that I am not alone with this opinion; Don, Kenji and Jonathan, among others, agree that the current behavior is confusing. A few reports of this issue (and related ones): http://d.puremagic.com/issues/show_bug.cgi?id=3467 http://d.puremagic.com/issues/show_bug.cgi?id=2257 http://d.puremagic.com/issues/show_bug.cgi?id=2550 Kenji even came up with a fix in form of a pull request, which you turned down without further explanation: https://github.com/D-Programming-Language/dmd/pull/449 So, could you please at least elaborate on why you think the current behavior is correct, and how its advantages outweigh the massive confusion it causes? Thanks, David
Dec 10 2011
This is a long-standing bug, and makes unsigned template value parameters and, to a lesser extent, integral template value parameters in general virtually unusable, because you hit hard to debug issues with confusing error messages all over the place.I absolutely agree.
Dec 10 2011
On 12/10/2011 5:04 AM, David Nadlinger wrote:So, could you please at least elaborate on why you think the current behavior is correct, and how its advantages outweigh the massive confusion it causes?The original idea was that you could add specializations without breaking existing binaries. However, experience seems to indicate that this isn't worth it.
Dec 10 2011
On 12/10/11 5:11 AM, Walter Bright wrote:On 12/10/2011 3:04 AM, Mehrdad wrote:Please let's not deflect the issue. If there's a patentable way to annoy an annoyed user even more, this must be it. A bug is a bug is a bug. Matrix can _only_ be instantiated with size_t, it says so in the definition: struct Matrix(T, size_t N = 1) { ... } THAT IS NOT: struct Matrix(T, alias N = cast(size_t) 1) { ... } as the current compiler seems to wrongly implement it. The fact that one can actually instantiate it with int or whatnot should first convert the int to size_t, and then instantiate the matrix with size_t. http://d.puremagic.com/issues/show_bug.cgi?id=7090 Thanks Mehrdad. AndreiOn 12/10/2011 3:01 AM, Walter Bright wrote:That's because Matrix!(int, cast(int)1) is considered a different template instantiation (and hence a different type) from Matrix!(int, cast(uint)1) Instantiation types are based on the arguments' types, not the parameters' types. If you use my suggestion for the shorthand notation, your code will work as you expect.On 12/10/2011 2:34 AM, Mehrdad wrote:That's not the issue. If you say 'int' instead of size_t, it works fine.... and another... struct Matrix(T, size_t N = 1) { public auto mul(Matrix!(T, N) other) const { return this; } }In general, to refer to the current template being expanded from inside of it, just give its name, as in: Matrix!(T,N) => Matrix
Dec 10 2011
On Saturday, 10 December 2011 at 10:34:28 UTC, Mehrdad wrote:... and another... struct Matrix(T, size_t N = 1) { public auto mul(Matrix!(T, N) other) const { return this; } } void main() { Matrix!(int, 2) m; m.mul(m); } annoy.d(7): Error: function annoy.Matrix!(int,2).Matrix.mul (Matrix!(int,N) other) const is not callable using argument types (Matrix!(int,2)) annoy.d(7): Error: cannot implicitly convert expression (m) of type Matrix!(int,2) to Matrix!(int,N)This one now works. :)
Apr 28 2012
... and another... struct S(L...) { enum N = L[0]; S!(N) me() { return this; } } alias S!(1) S2; annoy.d(3): Error: template instance annoy.S!(N) recursive expansion
Dec 10 2011
... and another... (yes, this one _IS_ a const issue) struct S { int opApply(scope int delegate(ref inout(int)) dg) inout { return 0; } } void main() { foreach (i; S()) { } } Error: inout on parameter means inout must be on return type as well (if from D1 code, replace with 'ref') -------------- I'll go sleep now... but I could probably go on for a while more. I'll try to post more of these bugs when I get the chance (assuming they're welcome...).
Dec 10 2011
On 12/10/2011 3:14 AM, Mehrdad wrote:... and another... (yes, this one _IS_ a const issue) struct S { int opApply(scope int delegate(ref inout(int)) dg) inout { return 0; } } void main() { foreach (i; S()) { } } Error: inout on parameter means inout must be on return type as well (if from D1 code, replace with 'ref')Right. inout has no point if it also does not appear on the return type. The purpose of inout is to transfer the 'constness' of the argument to the return type. If inout isn't on the return type somewhere, there's likely a design mistake in your code. It's like having: { a + b; } Which is an error in D because such is pointless.
Dec 10 2011
On 12/10/2011 3:33 AM, Walter Bright wrote:On 12/10/2011 3:14 AM, Mehrdad wrote:Sure, it's always a design mistake. :-) So how are you supposed to implement opApply on a container (or e.g. here, a matrix)? Copy/paste the code for const- and non-const versions?... and another... (yes, this one _IS_ a const issue) struct S { int opApply(scope int delegate(ref inout(int)) dg) inout { return 0; } } void main() { foreach (i; S()) { } } Error: inout on parameter means inout must be on return type as well (if from D1 code, replace with 'ref')Right. inout has no point if it also does not appear on the return type. The purpose of inout is to transfer the 'constness' of the argument to the return type. If inout isn't on the return type somewhere, there's likely a design mistake in your code. It's like having: { a + b; } Which is an error in D because such is pointless.
Dec 10 2011
On 12/10/2011 11:03 AM, Mehrdad wrote:So how are you supposed to implement opApply on a container (or e.g. here, a matrix)? Copy/paste the code for const- and non-const versions?Internal to a function, inout behaves like 'const'. You won't be able to modify the data. Therefore, if there is no inout in the return type, use 'const' in the parameter list instead. The purpose of inout is to transmit the 'constness' of the function argument type to the return type, using only one implementation of that function. That requires the function to internally regard inout as const.
Dec 10 2011
Am 10.12.2011 21:25, schrieb Walter Bright:On 12/10/2011 11:03 AM, Mehrdad wrote:But what about: void f(ref inout(int)* a, inout(int)* b) { a = b; } This cant work with const because that would violate the const system. I think the rule should be that either the return type must be inout or at least one ref/out parameter. Am I overlooking something? MafiSo how are you supposed to implement opApply on a container (or e.g. here, a matrix)? Copy/paste the code for const- and non-const versions?Internal to a function, inout behaves like 'const'. You won't be able to modify the data. Therefore, if there is no inout in the return type, use 'const' in the parameter list instead. The purpose of inout is to transmit the 'constness' of the function argument type to the return type, using only one implementation of that function. That requires the function to internally regard inout as const.
Dec 11 2011
On 12/11/2011 9:07 AM, Mafi wrote:But what about: void f(ref inout(int)* a, inout(int)* b) { a = b; } This cant work with const because that would violate the const system. I think the rule should be that either the return type must be inout or at least one ref/out parameter. Am I overlooking something?It should work with const.
Dec 11 2011
Am 11.12.2011 19:00, schrieb Walter Bright:On 12/11/2011 9:07 AM, Mafi wrote:But allowing a variable of type abc(int)* where abc is immutable or nothing (ie mutable) to be passed to a parameter of the type ref/out const(int)* breaks the type system. void f(ref const(int)* a, const(int)* b) {a = b; } void main() { immutable(int)* a; auto b = (new int[](5)).ptr; f(a, b); //if this compiles I have just assigned a mutable pointer //to an immutable one } With the above definition using inout, such a call would be disallowed.But what about: void f(ref inout(int)* a, inout(int)* b) { a = b; } This cant work with const because that would violate the const system. I think the rule should be that either the return type must be inout or at least one ref/out parameter. Am I overlooking something?It should work with const.
Dec 11 2011
On 12/11/11 12:40 PM, Mafi wrote:void f(ref const(int)* a, const(int)* b) {a = b; } void main() { immutable(int)* a; auto b = (new int[](5)).ptr; f(a, b); //if this compiles I have just assigned a mutable pointer //to an immutable one }immutable(int)* does not convert to immutable(const)*. Andrei
Dec 11 2011
On 12/11/2011 08:35 PM, Andrei Alexandrescu wrote:On 12/11/11 12:40 PM, Mafi wrote:I think you meant an lvalue of type immutable(int)* does not convert to ref const(int)*. DMD currently allows it. http://d.puremagic.com/issues/show_bug.cgi?id=4251void f(ref const(int)* a, const(int)* b) {a = b; } void main() { immutable(int)* a; auto b = (new int[](5)).ptr; f(a, b); //if this compiles I have just assigned a mutable pointer //to an immutable one }immutable(int)* does not convert to immutable(const)*. Andrei
Dec 11 2011
Am 11.12.2011 20:35, schrieb Andrei Alexandrescu:On 12/11/11 12:40 PM, Mafi wrote:[assuming you meant ref const(int)*] But look: I want a function f(a,b) so that it copies int* b into a. I want this function to be const correct and not to be a template. If I'm not mistaken, in the planned D (ie without bugs and with everything implememnted correctly) there's no way to express such a function. I see two solutions: 1. Allow calling void f(ref const(int)* a, const(int)* b) {a = b; } with immutable parameter a. 2. Allow void f(ref inout(int)* a, inout(int)* b) { a = b; } using my rule I suggested before. I think Walter suggests number 1 but as I showed it violates the const system. I think allowing inout with ref/out parameters is right even without the return value being inout. These ref/out parameters are conceptionally results. I should also be allowed if delegate/function has a inout parameter. So void g(inout int a, delegate(inout(int)*)) should be allowed. This delegate is a callback and it's parameter is conceptionally the result of this function. In my opinion we definitly have to change the current strict inout-rule. Mafivoid f(ref const(int)* a, const(int)* b) {a = b; } void main() { immutable(int)* a; auto b = (new int[](5)).ptr; f(a, b); //if this compiles I have just assigned a mutable pointer //to an immutable one }immutable(int)* does not convert to immutable(const)*. Andrei
Dec 11 2011
On Sun, 11 Dec 2011 19:40:42 +0100 Mafi <mafi example.org> wrote:void f(ref const(int)* a, const(int)* b) {a = b; } void main() { immutable(int)* a; auto b = (new int[](5)).ptr; f(a, b); //if this compiles I have just assigned a mutable pointer //to an immutable one } With the above definition using inout, such a call would be disallowed.Just gave this code a try: void f(ref const(int)* a, const(int)* b) {a = b; } void main() { const(int)* a; immutable(int)* b; auto c = (new int[](5)).ptr; f(a, c); f(b, c); //test.d(7): Error: cast(const(int)*)b is not an lvalue }
Dec 12 2011
On 12/13/2011 01:58 AM, Jesse Phillips wrote:On Sun, 11 Dec 2011 19:40:42 +0100 Mafi<mafi example.org> wrote:Interesting. That is another bug that masks the one you were trying to exploit. :o) Consider: void f(const ref int* a) { } void main() { immutable(int)* a; f(a); // same error, but should compile } Anyway this does the same bad stuff as your example was trying to do and currently compiles: void f(const(int)** a, const(int)* b) {*a = b; } void main() { const(int)* a; immutable(int)* b; auto c = (new int[](5)).ptr; f(&a, c); f(&b, c); // now goes through }void f(ref const(int)* a, const(int)* b) {a = b; } void main() { immutable(int)* a; auto b = (new int[](5)).ptr; f(a, b); //if this compiles I have just assigned a mutable pointer //to an immutable one } With the above definition using inout, such a call would be disallowed.Just gave this code a try: void f(ref const(int)* a, const(int)* b) {a = b; } void main() { const(int)* a; immutable(int)* b; auto c = (new int[](5)).ptr; f(a, c); f(b, c); //test.d(7): Error: cast(const(int)*)b is not an lvalue }
Dec 13 2011
On Sun, 11 Dec 2011 12:07:37 -0500, Mafi <mafi example.org> wrote:Am 10.12.2011 21:25, schrieb Walter Bright:That was brought up during discussion on adding the feature. One of the reasons inout is viable is because a) the source and result of where the constancy flows is well defined and b) the exit point is an rvalue Allowing ref parameters fails both those rules. Consider this: void bad(ref inout(int)* a, ref inout(int)* b); which is the entry and which is the exit? Is a set to b, or b set to a? Now, also consider that you can't affect the constancy of the result, because the type of the parameter is already defined. e.g.: // note that your example shouldn't even be valid, because you can't implicitly cast through two mutable references int a; auto pa = &a; immutable int b; auto pb = &b; f(a, b); How can this affect a? its type is already decided. Compare that to: inout(int)* g(inout(int)* b) { return b;} auto pa = g(pb); clean and simple. -SteveOn 12/10/2011 11:03 AM, Mehrdad wrote:But what about: void f(ref inout(int)* a, inout(int)* b) { a = b; } This cant work with const because that would violate the const system. I think the rule should be that either the return type must be inout or at least one ref/out parameter. Am I overlooking something?So how are you supposed to implement opApply on a container (or e.g. here, a matrix)? Copy/paste the code for const- and non-const versions?Internal to a function, inout behaves like 'const'. You won't be able to modify the data. Therefore, if there is no inout in the return type, use 'const' in the parameter list instead. The purpose of inout is to transmit the 'constness' of the function argument type to the return type, using only one implementation of that function. That requires the function to internally regard inout as const.
Dec 12 2011
On 12/12/2011 01:50 PM, Steven Schveighoffer wrote:On Sun, 11 Dec 2011 12:07:37 -0500, Mafi <mafi example.org> wrote:This currently compiles: inout(void) very_bad(ref inout(int)* a, ref inout(int)* b){a = b;} void main(){ immutable int a=2; int *x; immutable(int)* y=&a; very_bad(x,y); *x=1; assert(*y==a); // fail } How does the design catch this error? Will inout** = inout** assignments be disallowed?Am 10.12.2011 21:25, schrieb Walter Bright:That was brought up during discussion on adding the feature. One of the reasons inout is viable is because a) the source and result of where the constancy flows is well defined and b) the exit point is an rvalue Allowing ref parameters fails both those rules. Consider this: void bad(ref inout(int)* a, ref inout(int)* b); which is the entry and which is the exit? Is a set to b, or b set to a? Now, also consider that you can't affect the constancy of the result, because the type of the parameter is already defined. e.g.: // note that your example shouldn't even be valid, because you can't implicitly cast through two mutable references int a; auto pa = &a; immutable int b; auto pb = &b; f(a, b); How can this affect a? its type is already decided. Compare that to: inout(int)* g(inout(int)* b) { return b;} auto pa = g(pb); clean and simple. -SteveOn 12/10/2011 11:03 AM, Mehrdad wrote:But what about: void f(ref inout(int)* a, inout(int)* b) { a = b; } This cant work with const because that would violate the const system. I think the rule should be that either the return type must be inout or at least one ref/out parameter. Am I overlooking something?So how are you supposed to implement opApply on a container (or e.g. here, a matrix)? Copy/paste the code for const- and non-const versions?Internal to a function, inout behaves like 'const'. You won't be able to modify the data. Therefore, if there is no inout in the return type, use 'const' in the parameter list instead. The purpose of inout is to transmit the 'constness' of the function argument type to the return type, using only one implementation of that function. That requires the function to internally regard inout as const.
Dec 12 2011
On 12/12/2011 03:46 PM, Timon Gehr wrote:On 12/12/2011 01:50 PM, Steven Schveighoffer wrote:Probably it is better to catch it at the call site. But if that is implemented then the requirement to have inout on the return value gets nonsensical.On Sun, 11 Dec 2011 12:07:37 -0500, Mafi <mafi example.org> wrote:This currently compiles: inout(void) very_bad(ref inout(int)* a, ref inout(int)* b){a = b;} void main(){ immutable int a=2; int *x; immutable(int)* y=&a; very_bad(x,y); *x=1; assert(*y==a); // fail } How does the design catch this error? Will inout** = inout** assignments be disallowed?Am 10.12.2011 21:25, schrieb Walter Bright:That was brought up during discussion on adding the feature. One of the reasons inout is viable is because a) the source and result of where the constancy flows is well defined and b) the exit point is an rvalue Allowing ref parameters fails both those rules. Consider this: void bad(ref inout(int)* a, ref inout(int)* b); which is the entry and which is the exit? Is a set to b, or b set to a? Now, also consider that you can't affect the constancy of the result, because the type of the parameter is already defined. e.g.: // note that your example shouldn't even be valid, because you can't implicitly cast through two mutable references int a; auto pa = &a; immutable int b; auto pb = &b; f(a, b); How can this affect a? its type is already decided. Compare that to: inout(int)* g(inout(int)* b) { return b;} auto pa = g(pb); clean and simple. -SteveOn 12/10/2011 11:03 AM, Mehrdad wrote:But what about: void f(ref inout(int)* a, inout(int)* b) { a = b; } This cant work with const because that would violate the const system. I think the rule should be that either the return type must be inout or at least one ref/out parameter. Am I overlooking something?So how are you supposed to implement opApply on a container (or e.g. here, a matrix)? Copy/paste the code for const- and non-const versions?Internal to a function, inout behaves like 'const'. You won't be able to modify the data. Therefore, if there is no inout in the return type, use 'const' in the parameter list instead. The purpose of inout is to transmit the 'constness' of the function argument type to the return type, using only one implementation of that function. That requires the function to internally regard inout as const.
Dec 12 2011
On 12/12/2011 04:08 PM, Timon Gehr wrote:On 12/12/2011 03:46 PM, Timon Gehr wrote:OK, got it. What you call 'bad' should compile: void bad(ref inout(int)* a, ref inout(int)* b); int* x; immutable(int)* y; const(int)* z; bad(x,x); // fine bad(y,y); // fine bad(z,z); // fine bad(x,y); // inout is deduced to const, ergo neither x nor y convert to the parameter type -> compile error bad(x,z); // now only error for x ... The requirement of having inout on the return type should be removed for more expressiveness.On 12/12/2011 01:50 PM, Steven Schveighoffer wrote:Probably it is better to catch it at the call site. But if that is implemented then the requirement to have inout on the return value gets nonsensical.On Sun, 11 Dec 2011 12:07:37 -0500, Mafi <mafi example.org> wrote:This currently compiles: inout(void) very_bad(ref inout(int)* a, ref inout(int)* b){a = b;} void main(){ immutable int a=2; int *x; immutable(int)* y=&a; very_bad(x,y); *x=1; assert(*y==a); // fail } How does the design catch this error? Will inout** = inout** assignments be disallowed?Am 10.12.2011 21:25, schrieb Walter Bright:That was brought up during discussion on adding the feature. One of the reasons inout is viable is because a) the source and result of where the constancy flows is well defined and b) the exit point is an rvalue Allowing ref parameters fails both those rules. Consider this: void bad(ref inout(int)* a, ref inout(int)* b); which is the entry and which is the exit? Is a set to b, or b set to a? Now, also consider that you can't affect the constancy of the result, because the type of the parameter is already defined. e.g.: // note that your example shouldn't even be valid, because you can't implicitly cast through two mutable references int a; auto pa = &a; immutable int b; auto pb = &b; f(a, b); How can this affect a? its type is already decided. Compare that to: inout(int)* g(inout(int)* b) { return b;} auto pa = g(pb); clean and simple. -SteveOn 12/10/2011 11:03 AM, Mehrdad wrote:But what about: void f(ref inout(int)* a, inout(int)* b) { a = b; } This cant work with const because that would violate the const system. I think the rule should be that either the return type must be inout or at least one ref/out parameter. Am I overlooking something?So how are you supposed to implement opApply on a container (or e.g. here, a matrix)? Copy/paste the code for const- and non-const versions?Internal to a function, inout behaves like 'const'. You won't be able to modify the data. Therefore, if there is no inout in the return type, use 'const' in the parameter list instead. The purpose of inout is to transmit the 'constness' of the function argument type to the return type, using only one implementation of that function. That requires the function to internally regard inout as const.
Dec 12 2011
On Mon, 12 Dec 2011 10:21:35 -0500, Timon Gehr <timon.gehr gmx.ch> wrote:On 12/12/2011 04:08 PM, Timon Gehr wrote:I've thought about this for a few minutes, and I can't find a flaw in it. But I'm not seeing a huge benefit to it either. Why is it advantageous to use a ref parameter for something that should be a return? Can you show a good use case for this? I still am uneasy with allowing applying a wildcard to a reference underneath to mutable references. I know it doesn't work for const. -SteveOn 12/12/2011 03:46 PM, Timon Gehr wrote:OK, got it. What you call 'bad' should compile: void bad(ref inout(int)* a, ref inout(int)* b); int* x; immutable(int)* y; const(int)* z; bad(x,x); // fine bad(y,y); // fine bad(z,z); // fine bad(x,y); // inout is deduced to const, ergo neither x nor y convert to the parameter type -> compile error bad(x,z); // now only error for x ... The requirement of having inout on the return type should be removed for more expressiveness.On 12/12/2011 01:50 PM, Steven Schveighoffer wrote:Probably it is better to catch it at the call site. But if that is implemented then the requirement to have inout on the return value gets nonsensical.On Sun, 11 Dec 2011 12:07:37 -0500, Mafi <mafi example.org> wrote:This currently compiles: inout(void) very_bad(ref inout(int)* a, ref inout(int)* b){a = b;} void main(){ immutable int a=2; int *x; immutable(int)* y=&a; very_bad(x,y); *x=1; assert(*y==a); // fail } How does the design catch this error? Will inout** = inout** assignments be disallowed?Am 10.12.2011 21:25, schrieb Walter Bright:That was brought up during discussion on adding the feature. One of the reasons inout is viable is because a) the source and result of where the constancy flows is well defined and b) the exit point is an rvalue Allowing ref parameters fails both those rules. Consider this: void bad(ref inout(int)* a, ref inout(int)* b); which is the entry and which is the exit? Is a set to b, or b set to a? Now, also consider that you can't affect the constancy of the result, because the type of the parameter is already defined. e.g.: // note that your example shouldn't even be valid, because you can't implicitly cast through two mutable references int a; auto pa = &a; immutable int b; auto pb = &b; f(a, b); How can this affect a? its type is already decided. Compare that to: inout(int)* g(inout(int)* b) { return b;} auto pa = g(pb); clean and simple. -SteveOn 12/10/2011 11:03 AM, Mehrdad wrote:But what about: void f(ref inout(int)* a, inout(int)* b) { a = b; } This cant work with const because that would violate the const system. I think the rule should be that either the return type must be inout or at least one ref/out parameter. Am I overlooking something?So how are you supposed to implement opApply on a container (or e.g. here, a matrix)? Copy/paste the code for const- and non-const versions?Internal to a function, inout behaves like 'const'. You won't be able to modify the data. Therefore, if there is no inout in the return type, use 'const' in the parameter list instead. The purpose of inout is to transmit the 'constness' of the function argument type to the return type, using only one implementation of that function. That requires the function to internally regard inout as const.
Dec 12 2011
On 12/12/2011 05:46 PM, Steven Schveighoffer wrote:On Mon, 12 Dec 2011 10:21:35 -0500, Timon Gehr <timon.gehr gmx.ch> wrote:For anything that is both input and output, for example: void swap(T)(ref inout(T)[] x, ref inout(T)[] y); void apply(T)(inout(T)[] delegate(inout(T)[]) dg, ref inout(T)[] arg); I think it should be a no-brainer, all that is needed is to remove the check if inout is present on the return type. Every other part of the type checking is identical to when it is there (this is shown by the fact that qualifying void with inout makes the examples compile.)On 12/12/2011 04:08 PM, Timon Gehr wrote:I've thought about this for a few minutes, and I can't find a flaw in it. But I'm not seeing a huge benefit to it either. Why is it advantageous to use a ref parameter for something that should be a return? Can you show a good use case for this?On 12/12/2011 03:46 PM, Timon Gehr wrote:OK, got it. What you call 'bad' should compile: void bad(ref inout(int)* a, ref inout(int)* b); int* x; immutable(int)* y; const(int)* z; bad(x,x); // fine bad(y,y); // fine bad(z,z); // fine bad(x,y); // inout is deduced to const, ergo neither x nor y convert to the parameter type -> compile error bad(x,z); // now only error for x ... The requirement of having inout on the return type should be removed for more expressiveness.On 12/12/2011 01:50 PM, Steven Schveighoffer wrote:Probably it is better to catch it at the call site. But if that is implemented then the requirement to have inout on the return value gets nonsensical.On Sun, 11 Dec 2011 12:07:37 -0500, Mafi <mafi example.org> wrote:This currently compiles: inout(void) very_bad(ref inout(int)* a, ref inout(int)* b){a = b;} void main(){ immutable int a=2; int *x; immutable(int)* y=&a; very_bad(x,y); *x=1; assert(*y==a); // fail } How does the design catch this error? Will inout** = inout** assignments be disallowed?Am 10.12.2011 21:25, schrieb Walter Bright:That was brought up during discussion on adding the feature. One of the reasons inout is viable is because a) the source and result of where the constancy flows is well defined and b) the exit point is an rvalue Allowing ref parameters fails both those rules. Consider this: void bad(ref inout(int)* a, ref inout(int)* b); which is the entry and which is the exit? Is a set to b, or b set to a? Now, also consider that you can't affect the constancy of the result, because the type of the parameter is already defined. e.g.: // note that your example shouldn't even be valid, because you can't implicitly cast through two mutable references int a; auto pa = &a; immutable int b; auto pb = &b; f(a, b); How can this affect a? its type is already decided. Compare that to: inout(int)* g(inout(int)* b) { return b;} auto pa = g(pb); clean and simple. -SteveOn 12/10/2011 11:03 AM, Mehrdad wrote:But what about: void f(ref inout(int)* a, inout(int)* b) { a = b; } This cant work with const because that would violate the const system. I think the rule should be that either the return type must be inout or at least one ref/out parameter. Am I overlooking something?So how are you supposed to implement opApply on a container (or e.g. here, a matrix)? Copy/paste the code for const- and non-const versions?Internal to a function, inout behaves like 'const'. You won't be able to modify the data. Therefore, if there is no inout in the return type, use 'const' in the parameter list instead. The purpose of inout is to transmit the 'constness' of the function argument type to the return type, using only one implementation of that function. That requires the function to internally regard inout as const.I still am uneasy with allowing applying a wildcard to a reference underneath to mutable references. I know it doesn't work for const. -SteveThere is nothing being applied, inout is just matched. After the matching, inout vanishes (it is replaced by nothing, const or immutable) and if that _inout-free_ parameter list still typechecks with the arguments, then it can be invoked. It is simply a case of parametric polymorphism. void good(ref inout(int)* a, ref inout(int)* b); is simultaneously these three functions: (duplicating the code that way is the poor man's inout) void good(ref int* a, ref int* b){ a = b; } void good(ref immutable(int)* a, ref immutable(int)* b){ a = b; } void good(ref const(int)* a, ref const(int)* b){ a = b; } None of these applies any type modifier at any level. It suffices if any of those three typechecks for the call to the polymorphic one to succeed safely. (because the body of 'good' treats its arguments like const).
Dec 12 2011
On Mon, 12 Dec 2011 12:49:11 -0500, Timon Gehr <timon.gehr gmx.ch> wrote:On 12/12/2011 05:46 PM, Steven Schveighoffer wrote:This is a great way to think about inout in general: 1. all inout parmeters are matched against int, immutable, and inout. If all of them match that qualifier, then that qualifier is substituted for inout. 2. If 1. does not match any of those, const substituted for inout. 3. Normal function call rules apply. That is, just because a match is found doesn't mean the function can be called (as you have demonstrated). 4. Anything that is inout inside a function is treated the same as it is now (similarly to immutable, except for const(inout(...)) ). I always thought of inout as being something performed by the compiler, while calling the function. But it's almost just a way to repaint the parameters/return type of an existing function before applying normal function call rules. I'm cautiously optimistic about this. I want to see what Kenji thinks, at this point he probably understands inout better than me. I think actually his first patch did not require inout on the return type, and I insisted it was required, that might have been a mistake to do that (removing this requirement indirectly fixes 6809 as well). -SteveI still am uneasy with allowing applying a wildcard to a reference underneath to mutable references. I know it doesn't work for const.There is nothing being applied, inout is just matched. After the matching, inout vanishes (it is replaced by nothing, const or immutable) and if that _inout-free_ parameter list still typechecks with the arguments, then it can be invoked. It is simply a case of parametric polymorphism. void good(ref inout(int)* a, ref inout(int)* b); is simultaneously these three functions: (duplicating the code that way is the poor man's inout) void good(ref int* a, ref int* b){ a = b; } void good(ref immutable(int)* a, ref immutable(int)* b){ a = b; } void good(ref const(int)* a, ref const(int)* b){ a = b; } None of these applies any type modifier at any level. It suffices if any of those three typechecks for the call to the polymorphic one to succeed safely. (because the body of 'good' treats its arguments like const).
Dec 12 2011
On 12/10/2011 3:33 AM, Walter Bright wrote:On 12/10/2011 3:14 AM, Mehrdad wrote:Oh yeah, I just realized -- I think you missed the _second_ 'inout'. It's indeed on a parameter! It's just on the implicit 'this' parameter, not on an explicit one. This is a bug (IMO, arising from the fact that inout() is treated like a type constructor, whereas it shouldn't be).... and another... (yes, this one _IS_ a const issue) struct S { int opApply(scope int delegate(ref inout(int)) dg) inout { return 0; } } void main() { foreach (i; S()) { } } Error: inout on parameter means inout must be on return type as well (if from D1 code, replace with 'ref')Right. inout has no point if it also does not appear on the return type. The purpose of inout is to transfer the 'constness' of the argument to the return type. If inout isn't on the return type somewhere, there's likely a design mistake in your code. It's like having: { a + b; } Which is an error in D because such is pointless.
Dec 10 2011
On 12/10/2011 08:47 PM, Mehrdad wrote:On 12/10/2011 3:33 AM, Walter Bright wrote:Yah, the issue is that *BOTH* your inouts are on a parameter. Just change them to const, without copying any code and call it a day...On 12/10/2011 3:14 AM, Mehrdad wrote:Oh yeah, I just realized -- I think you missed the _second_ 'inout'. It's indeed on a parameter! It's just on the implicit 'this' parameter, not on an explicit one. This is a bug (IMO, arising from the fact that inout() is treated like a type constructor, whereas it shouldn't be).... and another... (yes, this one _IS_ a const issue) struct S { int opApply(scope int delegate(ref inout(int)) dg) inout { return 0; } } void main() { foreach (i; S()) { } } Error: inout on parameter means inout must be on return type as well (if from D1 code, replace with 'ref')Right. inout has no point if it also does not appear on the return type. The purpose of inout is to transfer the 'constness' of the argument to the return type. If inout isn't on the return type somewhere, there's likely a design mistake in your code. It's like having: { a + b; } Which is an error in D because such is pointless.
Dec 10 2011
On 12/10/2011 1:00 PM, Timon Gehr wrote:Yah, the issue is that *BOTH* your inouts are on a parameter. Just change them to const, without copying any code and call it a day...I don't see what's wrong with that, but yes, that's what I did. And that caused the next bug that I posted. :P
Dec 10 2011
On 12/10/2011 10:04 PM, Mehrdad wrote:On 12/10/2011 1:00 PM, Timon Gehr wrote:No, that is not what you did. You did copy the code.Yah, the issue is that *BOTH* your inouts are on a parameter. Just change them to const, without copying any code and call it a day...I don't see what's wrong with that, but yes, that's what I did. And that caused the next bug that I posted. :P
Dec 10 2011
On Sat, 10 Dec 2011 14:47:15 -0500, Mehrdad <wfunction hotmail.com> wrote:On 12/10/2011 3:33 AM, Walter Bright wrote:I'll split it out so it's easier to see: alias int delegate(ref inout(int)) dgtype; int opApply(dgtype dg) inout; Notice that neither dgtype or opapply has inout on the return type. Hence the error. However, I know what you are trying to do. You are trying to say that "while inside the delegate, use the constancy of the actual type". It won't work, because inout applies as const the *entire time* you are inside an inout function. Even when calling an external delegate. opApply is not like a range, it executes the foreach loop entirely inside the context of opApply. That is one advantage of ranges (but it's hard to take advantage, since you would currently have to define a range for each const type). AFAIK, the only way to do this properly is to have two different versions, one for const, one for non-const. And yes, inout should be a type constructor. It has to be, I don't know where you got the idea it shouldn't. -SteveOn 12/10/2011 3:14 AM, Mehrdad wrote:Oh yeah, I just realized -- I think you missed the _second_ 'inout'. It's indeed on a parameter! It's just on the implicit 'this' parameter, not on an explicit one. This is a bug (IMO, arising from the fact that inout() is treated like a type constructor, whereas it shouldn't be).... and another... (yes, this one _IS_ a const issue) struct S { int opApply(scope int delegate(ref inout(int)) dg) inout { return 0; } } void main() { foreach (i; S()) { } } Error: inout on parameter means inout must be on return type as well (if from D1 code, replace with 'ref')Right. inout has no point if it also does not appear on the return type. The purpose of inout is to transfer the 'constness' of the argument to the return type. If inout isn't on the return type somewhere, there's likely a design mistake in your code. It's like having: { a + b; } Which is an error in D because such is pointless.
Dec 11 2011
Oh, nice, here's an ICE for y'all :) struct Matrix(T) { property T[] data() { return null; } int opApply(scope int delegate(ref size_t[], ref T) dg) { return 0; } int opApply(scope int delegate(ref const(size_t[]), ref const(T)) dg) const { return 0; } Matrix!(typeof(mixin("data[0] " ~ op ~ " data[0]"))) opBinary(string op)(Matrix other) { auto result = typeof(return)(); foreach (i, ref val; this) { mixin("result[i] = val " ~ op ~ " other[i];"); } return result; } } void main() { auto m = Matrix!size_t(); m = m * m; }
Dec 10 2011
On 12/10/2011 11:43 AM, Mehrdad wrote:Oh, nice, here's an ICE for y'all :) struct Matrix(T) { property T[] data() { return null; } int opApply(scope int delegate(ref size_t[], ref T) dg) { return 0; } int opApply(scope int delegate(ref const(size_t[]), ref const(T)) dg) const { return 0; } Matrix!(typeof(mixin("data[0] " ~ op ~ " data[0]"))) opBinary(string op)(Matrix other) { auto result = typeof(return)(); foreach (i, ref val; this) { mixin("result[i] = val " ~ op ~ " other[i];"); } return result; } } void main() { auto m = Matrix!size_t(); m = m * m; }BTW this _DOES_ look like another issue related to const. Oh and the error is annoy.d(12): Error: cannot uniquely infer foreach argument types Assertion failure: '0' on line 144 in file 'statement.c'
Dec 10 2011
On 12/10/2011 11:52 AM, Trass3r wrote:http://d.puremagic.com/issues/show_bug.cgi?id=7091Thanks. Sorry I'm too lazy to do that myself lol -- kinda busy studying for exams and whatnot. (I really shouldn't be even working on this right now. :P) Also, I just wanted to note: I've found a bunch of these bugs as a result of trying to *work around* the previous bugs (!!!). For example, this one was a result of trying to fix the opApply-with-inout issue, by copy/pasting the code for the const and non-const versions. But I guess it turned out mixins didn't play well with that? _This_ fact (where working around one bug causing another) is why D's const looks broken to me. Hope it makes sense why I've been complaining about it so much!
Dec 10 2011
Then I think posting it directly to bugzilla instead would be better. Things get easily lost in the newsgroups.http://d.puremagic.com/issues/show_bug.cgi?id=7091Thanks. Sorry I'm too lazy to do that myself lol -- kinda busy studying for exams and whatnot. (I really shouldn't be even working on this right now. :P)
Dec 10 2011
On 12/10/2011 12:01 PM, Trass3r wrote:See, but, it's a tradeoff: then I wouldn't be able to complain about it :'(Then I think posting it directly to bugzilla instead would be better. Things get easily lost in the newsgroups.http://d.puremagic.com/issues/show_bug.cgi?id=7091Thanks. Sorry I'm too lazy to do that myself lol -- kinda busy studying for exams and whatnot. (I really shouldn't be even working on this right now. :P)
Dec 10 2011
On Saturday, 10 December 2011 at 19:43:51 UTC, Mehrdad wrote:Oh, nice, here's an ICE for y'all :)No more ICE on 2.059 :)
Apr 28 2012
On 12/10/2011 1:37 AM, Mehrdad wrote:(Sorry for being so critical but at least I tried to make it constructive...)Actually, I think your post is very constructive and worthwhile. Thanks for taking the time to be specific about what issues you're running in to.
Dec 10 2011
On 12/10/2011 12:27 PM, Walter Bright wrote:On 12/10/2011 1:37 AM, Mehrdad wrote:Thanks! Glad to know it's helpful. :) Will post more when I get the chance (probably after exams, lol).(Sorry for being so critical but at least I tried to make it constructive...)Actually, I think your post is very constructive and worthwhile. Thanks for taking the time to be specific about what issues you're running in to.
Dec 10 2011
Moar bugs/ICEs... struct S(int N) { this(T!N) { } } alias S!1 M; annoy.d(1): Error: template instance T!(N) template 'T' is not defined, did you mean M? annoy.d(1): Error: T!(N) is used as a type ty = 35 Assertion failure: '0' on line 145 in file 'mtype.c
Dec 10 2011
On Sunday, 11 December 2011 at 04:16:04 UTC, Mehrdad wrote:Moar bugs/ICEs... struct S(int N) { this(T!N) { } } alias S!1 M; annoy.d(1): Error: template instance T!(N) template 'T' is not defined, did you mean M? annoy.d(1): Error: T!(N) is used as a type ty = 35 Assertion failure: '0' on line 145 in file 'mtype.cNo more ICE on 2.059 Win32
Apr 28 2012
Another bug, this time regarding invariant().... (I think this is related to the previous one about int vs. size_t in template parameters, but I'm not sure...) template NArray(T, size_t N) { static if (N > 0) { alias NArray!(T, N - 1)[] NArray; } else { alias T NArray; } } struct Tensor(T, Length...) { enum N = Length[0]; invariant() { } this(scope NArray!(T, N) copyOfItems...) { } property T[] data() { return null; } Tensor!(typeof(mixin("data[0] " ~ op ~ " data[0]")), N) opBinary(string op)(Tensor other) { return typeof(return).init; } } void main() { Tensor!(size_t, 1) a; a = a + a; } Error: this for __invariant needs to be type Tensor not type Tensor!(uint,1)
Dec 10 2011
On Sunday, 11 December 2011 at 04:27:31 UTC, Mehrdad wrote:Another bug, this time regarding invariant().... (I think this is related to the previous one about int vs. size_t in template parameters, but I'm not sure...) template NArray(T, size_t N) { static if (N > 0) { alias NArray!(T, N - 1)[] NArray; } else { alias T NArray; } } struct Tensor(T, Length...) { enum N = Length[0]; invariant() { } this(scope NArray!(T, N) copyOfItems...) { } property T[] data() { return null; } Tensor!(typeof(mixin("data[0] " ~ op ~ " data[0]")), N) opBinary(string op)(Tensor other) { return typeof(return).init; } } void main() { Tensor!(size_t, 1) a; a = a + a; } Error: this for __invariant needs to be type Tensor not type Tensor!(uint,1)This one is still valid.
Apr 28 2012
Okay, final exams are coming up again, and so are my bugs (I have no idea what the correlation is, don't ask...) I guess I should post this on bugzilla, but oh well... I continued the thread instead. Try compiling this (I did this on Windows, DMD 2.059): void main() { Foo!(int[])([[1], [2]]); } struct Foo(T) { auto foo() { Foo!(T[]) t; return t; } }
Apr 27 2012
Oh sorry, here's the previous thread... somehow it got detached because of the subject line change: http://lists.puremagic.com/pipermail/digitalmars-d/2011-December/117172.html
Apr 27 2012
On Saturday, 28 April 2012 at 04:45:59 UTC, Mehrdad wrote:Okay, final exams are coming up again, and so are my bugs (I have no idea what the correlation is, don't ask...) I guess I should post this on bugzilla, but oh well... I continued the thread instead. Try compiling this (I did this on Windows, DMD 2.059): void main() { Foo!(int[])([[1], [2]]); } struct Foo(T) { auto foo() { Foo!(T[]) t; return t; } }You expected that to work? Extra extra, infinite recursion is infinite! You are asking the compiler to instantiate Foo with the type int[], then use that type to instantiate Foo with int[][], which then instantiates Foo with type int[][][]. Try thinking about your code before mouthing off here. Would you fault C for causing a stack overflow in this case: int rec(int a){ return rec(a + 1); } I mean what did you expect, that the compiler could magically create infinite types? I can't even see where you might have gone wrong here, since the code is so simple. What I can see is that the constructor wouldn't work because there are no fields. I can see that you have some very strange ideas about templates, Foo(T) instantiates Foo(T[]), which is a different type, so it goes through and instantiates Foo(T[][]) which is, again, a different type. Think before declaring D to have bugs. -- James Miller
Apr 27 2012
You expected that to work?Uhm, why not? template<class T> struct F { F<F<T> > f() { return F<F<T> >(); } }; int main() { F<int>().f().f().f().f().f(); // etc. return 0; }Try thinking about your code before mouthing off here.Not trying to to be rude, but did you think about *your* reason before responding?
Apr 27 2012
On Saturday, 28 April 2012 at 06:03:54 UTC, Mehrdad wrote:dmd is not smart enough to avoid recursion by treating f as a templated function. I am not sure whether it should, but the following should certainly work: struct F(T) { auto f()() { return F!(F!T)(); } } void main() { F!int().f().f().f(); } Error: struct a.F(T) recursive template expansion for template argument - why?You expected that to work?Uhm, why not? template<class T> struct F { F<F<T> > f() { return F<F<T> >(); } }; int main() { F<int>().f().f().f().f().f(); // etc. return 0; }
Apr 28 2012
On 04/28/2012 09:46 AM, Max Samukha wrote:On Saturday, 28 April 2012 at 06:03:54 UTC, Mehrdad wrote:DMD behaves according to the language specification here.dmd is not smart enoughYou expected that to work?Uhm, why not? template<class T> struct F { F<F<T> > f() { return F<F<T> >(); } }; int main() { F<int>().f().f().f().f().f(); // etc. return 0; }to avoid recursion by treating f as a templated function. I am not sure whether it should,Maybe, but that would be a strange special case.but the following should certainly work: struct F(T) { auto f()() { return F!(F!T)(); } } void main() { F!int().f().f().f(); } Error: struct a.F(T) recursive template expansion for template argument - why?The checking for infinite recursion seems to be too conservative here. You could open a bug report.
Apr 28 2012
On Saturday, 28 April 2012 at 09:40:49 UTC, Timon Gehr wrote:On 04/28/2012 09:46 AM, Max Samukha wrote:That is not specified anywhere in the language specification.On Saturday, 28 April 2012 at 06:03:54 UTC, Mehrdad wrote:DMD behaves according to the language specification here.dmd is not smart enoughYou expected that to work?Uhm, why not? template<class T> struct F { F<F<T> > f() { return F<F<T> >(); } }; int main() { F<int>().f().f().f().f().f(); // etc. return 0; }That is arguable. Non-templated functions are a special (degenerate) case of templated functions. The way virtual functions work doesn't allow C++/D to fully implement that notion. Mark f() 'virtual' in the C++ example and the code won't compile. Otherwise, C++ tries to be close to the ideal and I would expect D be no worse.to avoid recursion by treating f as a templated function. I am not sure whether it should,Maybe, but that would be a strange special case.but the following should certainly work: struct F(T) { auto f()() { return F!(F!T)(); } } void main() { F!int().f().f().f(); } Error: struct a.F(T) recursive template expansion for template argument - why?The checking for infinite recursion seems to be too conservative here. You could open a bug report.
Apr 28 2012
On 04/28/2012 12:05 PM, Max Samukha wrote:On Saturday, 28 April 2012 at 09:40:49 UTC, Timon Gehr wrote:"Semantic analysis is not done until instantiated." On second thought, you are right, this is really badly phrased and does not say what it probably wants to. Anyway, the fact that templates are analysed fully eagerly upon instantiation is by design and the documentation should explicitly state that.On 04/28/2012 09:46 AM, Max Samukha wrote:That is not specified anywhere in the language specification.On Saturday, 28 April 2012 at 06:03:54 UTC, Mehrdad wrote:DMD behaves according to the language specification here.dmd is not smart enoughYou expected that to work?Uhm, why not? template<class T> struct F { F<F<T> > f() { return F<F<T> >(); } }; int main() { F<int>().f().f().f().f().f(); // etc. return 0; }If we are talking about the same thing, then it probably is not. I thought you were proposing to automatically templatise the function iff there is recursion?That is arguable.to avoid recursion by treating f as a templated function. I am not sure whether it should,Maybe, but that would be a strange special case.Non-templated functions are a special (degenerate) case of templated functions.How to 'instantiate' a non-templated function? This would necessarily be a supported operation, if there actually was such a relation between the two concepts.The way virtual functions work doesn't allow C++/D to fully implement that notion.That implies that the notion does not have merit. In D it was left out by design. And even C++ does not consistently implement it for non-virtuals.Mark f() 'virtual' in the C++ example and the code won't compile.I know. Furthermore, add an invalid member function to the templated struct F and don't refer to it. There won't be any compile time error. (unless it is virtual, of course!)Otherwise, C++ tries to be close to the idealIMAO, it fails to be close to the ideal.and I would expect D be no worse.Yah, and it really is not. The other issue you brought up in this thread is certainly a real issue with the implementation though.
Apr 28 2012
On 04/28/2012 08:03 AM, Mehrdad wrote:D templates are analysed eagerly upon instantiation, whereas C++ templates are analysed lazily. This is not a bug, it is a feature.You expected that to work?Uhm, why not? template<class T> struct F { F<F<T> > f() { return F<F<T> >(); } }; int main() { F<int>().f().f().f().f().f(); // etc. return 0; }
Apr 28 2012
On Saturday, 28 April 2012 at 09:36:55 UTC, Timon Gehr wrote:On 04/28/2012 08:03 AM, Mehrdad wrote:Having browsed several hundreds bug reports lately, I've seen a number of reports complaining that recursive templates instantiation failed. I've resisted the urge to comment that they were invalid. Although I do think they are invalid.D templates are analysed eagerly upon instantiation, whereas C++ templates are analysed lazily. This is not a bug, it is a feature.You expected that to work?Uhm, why not? template<class T> struct F { F<F<T> > f() { return F<F<T> >(); } }; int main() { F<int>().f().f().f().f().f(); // etc. return 0; }
Apr 28 2012
On Saturday, 28 April 2012 at 09:36:55 UTC, Timon Gehr wrote:On 04/28/2012 08:03 AM, Mehrdad wrote:Furthermore, eager analysis is necessary for other D features like CTFE and compile-time reflection. Honestly, in C++ I would stay away from code like the above anyway, irrelevant of whether it compiles, seems too "magic-y" for my tastes. I don't like things not being explicit. -- James MillerD templates are analysed eagerly upon instantiation, whereas C++ templates are analysed lazily. This is not a bug, it is a feature.You expected that to work?Uhm, why not? template<class T> struct F { F<F<T> > f() { return F<F<T> >(); } }; int main() { F<int>().f().f().f().f().f(); // etc. return 0; }
Apr 28 2012
On Sunday, 29 April 2012 at 06:22:34 UTC, James Miller wrote:Ah, I was wondering about that. Thank you for confirming. I will gladly ditch lazy template eval for something like CTFE.D templates are analysed eagerly upon instantiation, whereas C++ templates are analysed lazily. This is not a bug, it is a feature.Furthermore, eager analysis is necessary for other D features like CTFE and compile-time reflection.Honestly, in C++ I would stay away from code like the above anyway, irrelevant of whether it compiles, seems too "magic-y" for my tastes. I don't like things not being explicit.+1
Apr 29 2012
Some ICE for y'all. void filter(R)(scope bool delegate(ref BAD!R) func) { } void main() { filter(r => r); }
Apr 30 2012
Also, what exactly is wrong with this code? private import std.range; void filter(R)(R, bool delegate(ElementType!R)) { } void main() { [1, 2, 3].filter(delegate bool(x) { return x < 3; }); }
Apr 30 2012
On 05/01/2012 03:09 AM, Mehrdad wrote:Also, what exactly is wrong with this code? private import std.range; void filter(R)(R, bool delegate(ElementType!R)) { } void main() { [1, 2, 3].filter(delegate bool(x) { return x < 3; }); }The current type deduction strategy for IFTI is (imho) too weak. private import std.range; void filter(R)(R, bool delegate(ElementType!R)) { } void main() { [1, 2, 3].filter!(int[])(delegate bool(x) { return x < 3; }); }
Apr 30 2012
Is it just "weak", or is it outright wrong? It's telling me "R" isn't a valid identifier... "Timon Gehr" wrote in message news:jnnefl$2j69$1 digitalmars.com... On 05/01/2012 03:09 AM, Mehrdad wrote:Also, what exactly is wrong with this code? private import std.range; void filter(R)(R, bool delegate(ElementType!R)) { } void main() { [1, 2, 3].filter(delegate bool(x) { return x < 3; }); }The current type deduction strategy for IFTI is (imho) too weak. private import std.range; void filter(R)(R, bool delegate(ElementType!R)) { } void main() { [1, 2, 3].filter!(int[])(delegate bool(x) { return x < 3; }); }
Apr 30 2012
On 05/01/2012 03:46 AM, Mehrdad wrote:Is it just "weak", or is it outright wrong?Unfortunately it is by design that it does not work afaik.It's telling me "R" isn't a valid identifier...That one is a diagnostics bug.
Apr 30 2012
Wha..?! I can't believe delegates work so poorly in D... they're practically unusable unless you're passing them as template parameters (which brings on its own set of bugs)... Seems like every time I try to escape a bug somehow, another one pops up :( "Timon Gehr" wrote in message news:jnnfc6$2jve$2 digitalmars.com... On 05/01/2012 03:46 AM, Mehrdad wrote:Is it just "weak", or is it outright wrong?Unfortunately it is by design that it does not work afaik.It's telling me "R" isn't a valid identifier...That one is a diagnostics bug.
Apr 30 2012
On 05/01/2012 04:02 AM, Mehrdad wrote:Wha..?! I can't believe delegates work so poorly in D...It is not the delegates, it is the type deduction algorithm for implicit function template instantiation. The issue is that the parameters do not cross-talk, which means R is not known during matching: How to detect the argument type from bool delegate(ElementType!R) alone if R is not known? The obvious solution would be to use an actual inference algorithm that uses information that can be gained from other parameters as well as information already known from the current parameter. You might want to file an enhancement request, because this exact thing seems to trip up many programmers. (i.e. it would be a lot more intuitive and convenient if it worked.)they're practically unusable unless you're passing them as template parameters (which brings on its own set of bugs)...I haven't encountered those so far.Seems like every time I try to escape a bug somehow, another one pops up :(The situation is improving. Furthermore, there is a very large subset of the language that is already very usable. Another way to make your code compile: private import std.range; void filter(R,S)(R, bool delegate(S)) if(is(S==ElementType!R)){ } void main() { [1, 2, 3].filter((int x) { return x < 3; }); }
May 01 2012
I guess the problem is with type deduction, but the trouble is that half the reason why type deduction is useful is in the case of lambdas. They become effectively useless if you have to type out their entire signature, since you could then just make them a separate function (or just use a mixin instead, to create a closure... which I've done before). This isn't really an 'enhancement', since the error message is clearly a bug. So I filed them both as bugs. http://d.puremagic.com/issues/show_bug.cgi?id=8009 http://d.puremagic.com/issues/show_bug.cgi?id=8010 The other bug I was referring to was something along the lines of the compiler telling me, "I can't really handle closures as template parameters very well (something about 'local variable' or whatever), so I'll just give you an obscure error every now and then". It only happened when it was inside another function, and some conditions were satisfied. (I don't remember the exact message, but when I searched it, I saw it had been already reported before...) Oh and thanks for the alternative, it's a good workaround to know. :) The trouble is that while it solves the bug I posted, it's still not solving my (actual) problem. The actual problem was that I wanted to do something along the lines of: private import std.range; auto filter(R, F)(R r, F f) { /*return a filtered range*/ } void main() { [1, 2, 3].filter(x => x < 3); } but I resorted to 'delegates' because this didn't work. However, when I did so, I decided to use a delegate anyway, while using 'scope' on the delegate and its parameter to avoid a heap allocation, but that didn't work either... so I just filed the bug report with a case that had less type inference. But the trouble is that the caller needing to say "delegate" is *already* too much typing, so the rest don't really help. :\ On 05/01/2012 04:02 AM, Mehrdad wrote:Wha..?! I can't believe delegates work so poorly in D...It is not the delegates, it is the type deduction algorithm for implicit function template instantiation. The issue is that the parameters do not cross-talk, which means R is not known during matching: How to detect the argument type from bool delegate(ElementType!R) alone if R is not known? The obvious solution would be to use an actual inference algorithm that uses information that can be gained from other parameters as well as information already known from the current parameter. You might want to file an enhancement request, because this exact thing seems to trip up many programmers. (i.e. it would be a lot more intuitive and convenient if it worked.)they're practically unusable unless you're passing them as template parameters (which brings on its own set of bugs)...I haven't encountered those so far.Seems like every time I try to escape a bug somehow, another one pops up :(The situation is improving. Furthermore, there is a very large subset of the language that is already very usable. Another way to make your code compile: private import std.range; void filter(R,S)(R, bool delegate(S)) if(is(S==ElementType!R)){ } void main() { [1, 2, 3].filter((int x) { return x < 3; }); }
May 01 2012
On 05/01/2012 01:20 PM, Mehrdad wrote:I guess the problem is with type deduction, but the trouble is that half the reason why type deduction is useful is in the case of lambdas. They become effectively useless if you have to type out their entire signature, since you could then just make them a separate function (or just use a mixin instead, to create a closure... which I've done before). This isn't really an 'enhancement', since the error message is clearly a bug. So I filed them both as bugs. http://d.puremagic.com/issues/show_bug.cgi?id=8009 http://d.puremagic.com/issues/show_bug.cgi?id=8010 The other bug I was referring to was something along the lines of the compiler telling me, "I can't really handle closures as template parameters very well (something about 'local variable' or whatever), so I'll just give you an obscure error every now and then". It only happened when it was inside another function, and some conditions were satisfied. (I don't remember the exact message, but when I searched it, I saw it had been already reported before...)Probably you mean this one: struct S{ int x; auto foo(alias a)(){return a(x);} } void main(){ auto s = S(2); int y = 3; writeln(s.foo!(x=>x+y)()); } Error: template instance foo!(__lambda2) cannot use local '__lambda2(__T1)' as parameter to non-global template foo(alias a) This is an arbitrary restriction and should be fixed. The 'this' pointer for 'foo' could be passed in a reserved slot at the beginning of the context for 'main'.Oh and thanks for the alternative, it's a good workaround to know. :) The trouble is that while it solves the bug I posted, it's still not solving my (actual) problem. The actual problem was that I wanted to do something along the lines of: private import std.range; auto filter(R, F)(R r, F f) { /*return a filtered range*/ } void main() { [1, 2, 3].filter(x => x < 3); }private import std.range; auto filter(R, F)(R r, F f) { /*return a filtered range*/ } void main() { [1, 2, 3].filter((int x) => x < 3); }
May 01 2012
Yes, that non-global issue was the exact issue I was referring to. It drives me nuts whenever I try to give in and use templates. Regarding your "fix": Is it *really* intended that user should say arr.filter((typeof(some_random_expression) x) => x < y); instead of arr.filter(x => x < y); ? I think it's pretty obvious why that doesn't really work in practice...
May 01 2012
On 05/02/2012 04:14 AM, Mehrdad wrote:Yes, that non-global issue was the exact issue I was referring to. It drives me nuts whenever I try to give in and use templates. Regarding your "fix": Is it *really* intended that user should say arr.filter((typeof(some_random_expression) x) => x < y); instead of arr.filter(x => x < y); ? I think it's pretty obvious why that doesn't really work in practice...I agree that it should be fixed, but it is technically not a bug in the implementation.
May 02 2012
On 01/05/2012 02:00, Mehrdad wrote:Some ICE for y'all. void filter(R)(scope bool delegate(ref BAD!R) func) { } void main() { filter(r => r); }Is this in bugzilla? It can't get fixed if no one knows about it! (Make sure to give it the ice keyword once you've submitted so it gets bumped to the top of the queue!) -- Robert http://octarineparrot.com/
May 01 2012
Yeah I just posted it yesterday. http://d.puremagic.com/issues/show_bug.cgi?id=8009 "Robert Clipsham" wrote in message news:jnoi6g$1f79$1 digitalmars.com... On 01/05/2012 02:00, Mehrdad wrote:Some ICE for y'all. void filter(R)(scope bool delegate(ref BAD!R) func) { } void main() { filter(r => r); }Is this in bugzilla? It can't get fixed if no one knows about it! (Make sure to give it the ice keyword once you've submitted so it gets bumped to the top of the queue!) -- Robert http://octarineparrot.com/
May 01 2012
More problems... similar, but this time related to templates. struct Filter(R) { this(R) { } } template filter(R) { alias Filter!(R).__ctor filter; } void main() { filter([1, 2, 3]); } Error: template linq.filter(R) is not a function template Why?
May 01 2012
On 05/02/12 04:11, Mehrdad wrote:More problems... similar, but this time related to templates. struct Filter(R) { this(R) { } } template filter(R) { alias Filter!(R).__ctor filter; } void main() { filter([1, 2, 3]); } Error: template linq.filter(R) is not a function template Why?Because it isn't? Where would 'R' come from in your example?... auto filter(R)(R r) { return Filter!R(r); } artur
May 02 2012
On Tue, 01 May 2012 22:11:18 -0400, Mehrdad <wfunction hotmail.com> wrote:More problems... similar, but this time related to templates. struct Filter(R) { this(R) { } } template filter(R) { alias Filter!(R).__ctor filter; } void main() { filter([1, 2, 3]); } Error: template linq.filter(R) is not a function templateIt's an annoying limitation. IFTI specifically *only* works with functions. Not aliases, not types, not anything else. Functions only. -Steve
May 02 2012
Let's say you have this hypothetical piece of code: interface IConnection { string send(string data); } class Student { private string id; private string cachedName; private IConnection conn; public this(string id) { this.id = id; this.conn = ...; } public property string name() const { if (!cachedName) { cachedName = conn.send("get_name: " ~ id); } return cachedName; } } void main() { auto student = new immutable(Student)("142341234"); writeln(student.name); } Notice that there are two const-related issues in the code that are literally *unsolvable* (unless you avoid const/immutable entirely, or unless you cast() -- but then your entire code would be filled with dangerous const casts...). How does D2 plan to address these issues in the 'ideal' implementation?
May 01 2012
On Tuesday, May 01, 2012 18:55:03 Mehrdad wrote:Let's say you have this hypothetical piece of code: interface IConnection { string send(string data); } class Student { private string id; private string cachedName; private IConnection conn; public this(string id) { this.id = id; this.conn = ...; } public property string name() const { if (!cachedName) { cachedName = conn.send("get_name: " ~ id); } return cachedName; } } void main() { auto student = new immutable(Student)("142341234"); writeln(student.name); } Notice that there are two const-related issues in the code that are literally *unsolvable* (unless you avoid const/immutable entirely, or unless you cast() -- but then your entire code would be filled with dangerous const casts...). How does D2 plan to address these issues in the 'ideal' implementation?Since casting away const and mutating something would break the const system (with it being even worse with immutable), you can never used a cached value in a const function like this. The closest that you would be able to do would be to have a const and non-const version where the non-const versioned cached the value, and the const version didn't (it would likely use the value if already cached, but it would never cache it). And in the case of immutable, you should probably have a separate constructor which actually sets everything. So, something like interface IConnection { string send(string data); } class Student { private string id; private string cachedName; private IConnection conn; public this(string id) { this.id = id; this.conn = ...; } public this(string id) immutable { this.id = id; this.conn = ...; cachedName = conn.send("get_name: " ~ id); } public property string name() { if(!cachedName) cachedName = conn.send("get_name: " ~ id); return cachedName; } public property string name() const { return cachedName is null ? conn.send("get_name: " ~ id) : cachedName; } } void main() { auto student = new immutable(Student)("142341234"); writeln(student.name); } Now, if you don't care about purity at all, you can have an external hash of some kind which holds your cached values. e.g. public property string name() const { if(auto cachedName = id in globalCache) return cachedName; else { auto cachedName = conn.send("get_name: " ~ id); globalCache[id] = cachedName; return cachedName; } } But that's not exactly a pleasant solution, since the state is no longer in the object where it belongs. The first would likely be the better way to do it. However, some folks that like lazy loading such as this seem to have come to the conclusion that they should just pretty much never use const, because it's too restrictive for they want to do. - Jonathan M Davis P.S. You really should create new threads rather than resurrecting threads that are months old. It becomes very easy to ignore them when I have to scroll way up to find a new post in the threaded view. The only reason that I don't miss them entirely is the fact that my e-mail client tells me how many e-mails I have which are unread.
May 01 2012
1. Wouldn't your solution (a) Be telling the programmer, "you *can't* lazy-load const objects"? (b) Require duplicating the code 3 times? (Yes, you can factor it out as a mixin, but who wants to do that??) 2. "Some folks that like lazy loading such as this seem to have come to the conclusion that they should just pretty much never use const, because it's too restrictive for they want to do." Yes, that's **exactly** why I've said 'const is broken' for at least a year now, but it seemed like people objected to my conclusion... The trouble, as I just showed, is that it isn't. "Lazy-loading" and caching aren't exactly obscure or rarely-used concepts -- which seems to be the way they are portrayed here. They are *bound* to be used in any nontrivial program. So, if D doesn't let you use those techniques without 'hacking around' the language, then it doesn't matter how awesome D is otherwise -- C++ programmers _simply won't_ switch to D, and other programmers will definitely have trouble using const, immutable, shared, etc... In other words, my point with this entire thread was: These issues need to get *fixed* at some point on the _theory_ side. Who cares if the implementation takes an extra year or two to be finished? If it's wrong in theory, it's never going to be right in practice. And it looks to me like there is no real clean 'fix' for this issue in D... the only solutions are along the lines of using C-style casts in C++ to throw-away const-ness, and we all know that isn't an actual solution... 3. Okay I'll try to avoid resurrecting threads in the future, thanks for letting me know. :)
May 01 2012
On Tuesday, May 01, 2012 20:22:40 Mehrdad wrote:1. Wouldn't your solution (a) Be telling the programmer, "you *can't* lazy-load const objects"? (b) Require duplicating the code 3 times? (Yes, you can factor it out as a mixin, but who wants to do that??) 2. "Some folks that like lazy loading such as this seem to have come to the conclusion that they should just pretty much never use const, because it's too restrictive for they want to do." Yes, that's **exactly** why I've said 'const is broken' for at least a year now, but it seemed like people objected to my conclusion... The trouble, as I just showed, is that it isn't. "Lazy-loading" and caching aren't exactly obscure or rarely-used concepts -- which seems to be the way they are portrayed here. They are *bound* to be used in any nontrivial program. So, if D doesn't let you use those techniques without 'hacking around' the language, then it doesn't matter how awesome D is otherwise -- C++ programmers _simply won't_ switch to D, and other programmers will definitely have trouble using const, immutable, shared, etc... In other words, my point with this entire thread was: These issues need to get *fixed* at some point on the _theory_ side. Who cares if the implementation takes an extra year or two to be finished? If it's wrong in theory, it's never going to be right in practice.D's const does exactly what it intends to do. Yes, that means that lazy loading and const are mutually exclusive. And I don't expect that that will ever change. To change it would be to make it so that const isn't guaranteed to be const. There is _zero_ chance of convincing Walter of anything of the sort, and many in the community will back him in that. In order for the compiler to give solid guarantees, it can't be any other way, and more importantly, the fact that we have immutable means that it can't be any other way. If that means that you can't use const in many situations (even if you might use it in those same situations in C++), then that means that you can't use const. D's const is not C++'s const and was never intended to be.And it looks to me like there is no real clean 'fix' for this issue in D... the only solutions are along the lines of using C-style casts in C++ to throw-away const-ness, and we all know that isn't an actual solution...It's worse than not an actual solution. It breaks the type system, throwing away the gurantees that the compiler gives you (easily resulting in bugs if you actually do it) and risks segfaults and the like if the underlying object is actually immutable. - Jonathan M Davis
May 01 2012
On Wednesday, 2 May 2012 at 03:22:40 UTC, Mehrdad wrote:Yes, that's **exactly** why I've said 'const is broken' for at least a year now, but it seemed like people objected to my conclusion... The trouble, as I just showed, is that it isn't. "Lazy-loading" and caching aren't exactly obscure or rarely-used concepts -- which seems to be the way they are portrayed here. They are *bound* to be used in any nontrivial program. So, if D doesn't let you use those techniques without 'hacking around' the language, then it doesn't matter how awesome D is otherwise -- C++ programmers _simply won't_ switch to D, and other programmers will definitely have trouble using const, immutable, shared, etc...I've never commented on this issue, but here's my viewpoint: If you intend to change something, it's NOT const. Don't make an object and say you won't change it then change it. It's nonsensical. 9 times out of 10, what you really mean is "I have an object that has some immutable/const data in it". A clear way to understand what I mean is a Matrix. Some people want "immutable matrices" which cache their determinant when it's time to calculate it. What they REALLY want is a matrix of immutable data. So, instead of a "immutable Matrix!int", they really need to be "Matrix!immutable(int)". Effectively it's the same thing, only it's more precisely defined and it's actually possible to do caching with it. The way C++ works is simply incorrect, and yes it might take some time to get used to the fact that const means const and immutable means immutable, but if you don't want to respect those types in the first place, there's no sense in using them.
May 01 2012
Okay, so let's say you're right void process1(Student s) { .. } void process2(const(Student) s) { ... } and that what I guess what I REALLY want is to say, "process1() won't change the student's name, birthday, or any other attribute, but process2() will". How do you propose I make that guarantee known to the compiler/caller? Is that not the entire point of saying "const(Student)" in the first place? "Chris Cain" wrote in message news:mivrzrerrqlvskxcndva forum.dlang.org... On Wednesday, 2 May 2012 at 03:22:40 UTC, Mehrdad wrote:Yes, that's **exactly** why I've said 'const is broken' for at least a year now, but it seemed like people objected to my conclusion... The trouble, as I just showed, is that it isn't. "Lazy-loading" and caching aren't exactly obscure or rarely-used concepts -- which seems to be the way they are portrayed here. They are *bound* to be used in any nontrivial program. So, if D doesn't let you use those techniques without 'hacking around' the language, then it doesn't matter how awesome D is otherwise -- C++ programmers _simply won't_ switch to D, and other programmers will definitely have trouble using const, immutable, shared, etc...I've never commented on this issue, but here's my viewpoint: If you intend to change something, it's NOT const. Don't make an object and say you won't change it then change it. It's nonsensical. 9 times out of 10, what you really mean is "I have an object that has some immutable/const data in it". A clear way to understand what I mean is a Matrix. Some people want "immutable matrices" which cache their determinant when it's time to calculate it. What they REALLY want is a matrix of immutable data. So, instead of a "immutable Matrix!int", they really need to be "Matrix!immutable(int)". Effectively it's the same thing, only it's more precisely defined and it's actually possible to do caching with it. The way C++ works is simply incorrect, and yes it might take some time to get used to the fact that const means const and immutable means immutable, but if you don't want to respect those types in the first place, there's no sense in using them.
May 01 2012
On Wednesday, 2 May 2012 at 04:05:11 UTC, Mehrdad wrote:Okay, so let's say you're right void process1(Student s) { .. } void process2(const(Student) s) { ... } and that what I guess what I REALLY want is to say, "process1() won't change the student's name, birthday, or any other attribute, but process2() will". How do you propose I make that guarantee known to the compiler/caller?What about the object do you want const? How should this object normally change things? It looks like the way you've coded it, your connection will get information off of a server. Does const(Student) mean that it shouldn't write to the server? If so, use something akin to a compile-time dependency injection ... specify that one of those students has a read-only connection. Something like Student!StudentDBConnection might work, and then maybe Student!const(StudentDBConnection).Is that not the entire point of saying "const(Student)" in the first place?"const(Student)" means I won't change _anything_ about the object. It's const. So in a sense, you can say that you won't change the fields name, birthday, etc. That means you can't change the fields.
May 01 2012
On Wednesday, 2 May 2012 at 04:18:18 UTC, Chris Cain wrote:... specify that one of those students has a read-only connection. Something like Student!StudentDBConnection might work, and then maybe Student!const(StudentDBConnection).I take the second one back, I've not written any kind of connection code before, but it occurs to me that StudentDBConnection must have some kind of mutable state in order to connect to something. Maybe a ROConnection object (or a struct and use the awesomeness of D's compile-time duck typing) or something along those lines?
May 01 2012
"Chris Cain" wrote in message news:nqgwunggifrgmwwhkcql forum.dlang.org...What about the object do you want const? How should this object normally change things? It looks like the way you've coded it, your connection will get information off of a server.I don't think you answered my question. What I said (or meant to ask) was this: - OK, FINE, let's say I don't know what D's const() means, then. Maybe it isn't suitable for what I need. I just want to know: _How do you specify that the function process1() won't modify its 'student' parameter_? Your response was this: - "Does const(Student) mean that it shouldn't write to the server?" To which my answer is: - I don't know, you tell me. (?) _Should_ I use 'const' to specify this? Or should I use something else? In other words:You answered that by sayingIs that not the entire point of saying "const(Student)" in the first place?"const(Student)" means I won't change _anything_ about the object. It's const. So in a sense, you can say that you won't change the fields name, birthday, etc. That means you can't change the fields.The problem with your answer is that it implies D DOESN'T SUPPORT O.O.P. with const!!! If D supported OOP, then why the heck would process1() know (or _care_) how Student works internally? Why (or how) should it know (or care) that Student uses a database connection? Wasn't that information an implementation detail? All process1() needs to know is the *PUBLIC INTERFACE* of Student. And all it cares to say is: "According to the public interface of Student, none of the methods I will use will modify the object." process1() has NO IDEA what goes on internally! It has NO IDEA that Student uses a database connection. Maybe it's a network connnection instead. Or maybe it's being unit-tested, and it's actually a dummy object, with no relation to the outside world. Or maybe it's a wrapper/proxy for another object. The point is: _why should the code for process1() depend on this_?! So, I'll ask my question again: How should process1() tell the outside world that it will not be asking its parameter to manipulate itself, WITHOUT breaking the abstraction barrier and 'peeking' into the private world of Student?
May 01 2012
On Tuesday, May 01, 2012 23:10:04 Mehrdad wrote:"Chris Cain" wrote in message news:nqgwunggifrgmwwhkcql forum.dlang.org...What you're thinking about here is logical const. You want a way to indicate that an object is not logically altered by the function that you're passing it to. With logical const, the inner state of the object _can_ change so long as the state of the object as seen from the outside is constante. However, D's const is _physical_ const. It guarantees that the object isn't altered _at all_. And that _is_ part of the interface, because no non-const function can be called on a const object, and every const function statically guarantees that it doesn't mutate the object that it's on. There is _no_ way in D to indicate that an object won't be altered by a function save for guaranteeing that it won't be altered _at all_ by using const. There is no way to indicate that it won't be altered logically save for comments. And in reality, having the compiler verify that an object's logical state doesn't change even when some of its internal state does is _very_ difficult (if not impossible) to statically verify. As such, the compiler can make no such guarantees. Not even C++ does that. Arguably, C++'s const is a glorified comment. Walter certainly sees it that way and doesn't think that there is _any_ value to C++'s const as a result. What it _does_ guarantee is that you won't accidentally alter the object (since you have to cast away const to alter it, or some of its member variables are going to have to be mutable), so I'd argue that it's still very useful. But it does _not_ actually guarantee that the object won't get mutated by that function. It's effectively just a comment which the compiler partially verifies. It would be _very_ cool to be able to have a compiler-checked, logical const, but it probably isn't realistically possible, except maybe in very restrictive circumstances. - Jonathan M DavisWhat about the object do you want const? How should this object normally change things? It looks like the way you've coded it, your connection will get information off of a server.I don't think you answered my question. What I said (or meant to ask) was this: - OK, FINE, let's say I don't know what D's const() means, then. Maybe it isn't suitable for what I need. I just want to know: _How do you specify that the function process1() won't modify its 'student' parameter_?
May 01 2012
In the world of OOP, when would "guarantee"ing (so to speak) 'physical' const-ness ever be handy? Wouldn't "physical" const-ness be an implementation detail of the object, and therefore, impossible to determine by the user of the object?
May 01 2012
On Tuesday, May 01, 2012 23:48:29 Mehrdad wrote:In the world of OOP, when would "guarantee"ing (so to speak) 'physical' const-ness ever be handy? Wouldn't "physical" const-ness be an implementation detail of the object, and therefore, impossible to determine by the user of the object?No, it's not an implementation detail. When you mark an object as being physically const, then you're guaranteeing that you will not alter it through that reference or pointer (and not at all, if it's a value type, because then there can't be another reference or pointer which mutates it). The type system guarantees this, because it disallows any const variable from being mutated. When dealing with a const object, you can only call const functions on its interface. Whatever implementation there is for those interface methods would also have to be const to implement that interface. And the type system would then disallow any non-const functions being called within the implementations of those functions as well as disallowing the mutation of member variables. So, the type system guarantees that the object will not be mutated at all by any const function. And since const is part of the interface of the object, it is very much _not_ an implementation detail. Physical constness is _required_ in order to have immutable ever be converted to const, because immutable objects can _never_ be mutated through _any_ reference or pointer. If D's const were not transitive as well as guarantee that any const reference cannot alter the object it points to, then you couldn't have immutable be convertible to const, because const would not protect it against mutation. So, C++'s const is impossible in D if immutable is going to be convertible to const. It's _logical_ constness that is near-impossible to have the compiler verify, because it has no way of determining that the variables that you mark as mutable or from which you cast away const and mutate don't affect the logical state of the object. _You_ may know, but the compiler can't determine that. - Jonathan M Davis
May 02 2012
I think you misunderstood my question. Yes, __IF__ you mark an object as physically const, then the world is beautiful... My question is, __WHEN__ can you ever do that, except in the most trivial situations? As soon as you try to add an extra layer of indirection (be it a proxy, implementing a method in an interface, overriding a base class method, etc.), there is NO WAY for the caller to know what obj.foo() does on obj. How can it possibly know whether obj will stay physically const?Wouldn't "physical" const-ness be an implementation detail of the object, and therefore, impossible to determine by the user of the object?No, it's not an implementation detail. When you mark an object as being physically const, then you're guaranteeing that you will not alter it through that reference or pointer.
May 02 2012
On Wednesday, May 02, 2012 01:12:15 Mehrdad wrote:Because foo must be const, or if can't be called on a const object. And if it's const, then it can't call any non-const functions or mutate any of its member variables. If you have interface I { int foo() const; } class C : I { int foo() const {...} } C's foo _must_ be const, or it's not implementing I's foo, and it won't compile. And if C's foo is const, then it can't call a non-const function or mutate any of its member variables. If it were pure on top of that, then it couldn't mutate any global or class variables either. The same goes for any derived class which overrides foo. const is part of foo's signature, and no derived class can escape that. So, _every_ class which implements I, and _every_ class derived from C will have a const foo which will be unable to mutate the state of that object. If instead you did class D { int bar() const { return e.bar(); } E e; } then E's bar would have to be const, or it wouldn't be callable from D's bar, since the e member variable is const inside of D's bar, since D's bar is const, and you can't call a non-const function on a const variable. An extra layer of indirection doesn't escape const at all, because that layer of indirection must use const or it won't be usable by the outer layer. So, the type system is able to guarantee that when you call a const function, the object it's being called on - as well as any object that it contains directly or indirectly - will not be mutated. - Jonathan M DavisI think you misunderstood my question. Yes, __IF__ you mark an object as physically const, then the world is beautiful... My question is, __WHEN__ can you ever do that, except in the most trivial situations? As soon as you try to add an extra layer of indirection (be it a proxy, implementing a method in an interface, overriding a base class method, etc.), there is NO WAY for the caller to know what obj.foo() does on obj. How can it possibly know whether obj will stay physically const?hWouldn't "physical" const-ness be an implementation detail of the object, and therefore, impossible to determine by the user of the object?No, it's not an implementation detail. When you mark an object as being physically const, then you're guaranteeing that you will not alter it through that reference or pointer.
May 02 2012
Yes, 'const' is part of the interface. The trouble is that when you make it part of the interface, you're making the assumption that **no one** who derives from your class will need mutable state. How can you ever guarantee that?
May 02 2012
On 2012-05-02 15:13:43 +0000, "Mehrdad" <wfunction hotmail.com> said:Yes, 'const' is part of the interface. The trouble is that when you make it part of the interface, you're making the assumption that **no one** who derives from your class will need mutable state. How can you ever guarantee that?When you're making the object 'const', you're not making the assumption that no one who derives from this class need mutable state. What you're doing is asserting that bits belonging to this object needs to be 'const' to preserve sequential consistency across threads or for other reasons. If the derived class casts away const, it breaks that contract. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
May 02 2012
When you're making the object 'const', you're not making the assumption that no one who derives from this class need mutable state. What you're doing is asserting that bits belonging to this object needs to be 'const' to preserve sequential consistency across threads or for other reasons.Bits belonging to *this* object? I thought const was transitive...If the derived class casts away const, it breaks that contract.So you're saying the same thing: Derived classes CANNOT have mutable state...
May 02 2012
On 2012-05-02 18:01, Mehrdad wrote:No: interface Foo { void foo () const; } class Bar : Foo { int x; void foo () const {} void bar () { x++; } } void main() { auto bar = new Bar; bar.bar(); writeln(bar.x); } -- /Jacob CarlborgWhen you're making the object 'const', you're not making the assumption that no one who derives from this class need mutable state. What you're doing is asserting that bits belonging to this object needs to be 'const' to preserve sequential consistency across threads or for other reasons.Bits belonging to *this* object? I thought const was transitive...If the derived class casts away const, it breaks that contract.So you're saying the same thing: Derived classes CANNOT have mutable state...
May 02 2012
Er, I guess I didn't say what I actually meant to say, my bad. x_x What I meant that you're assuming that derived classes won't need mutable state in an const method that they overrode.
May 02 2012
On 05/02/2012 08:58 PM, Mehrdad wrote:mutable state in an const method=O
May 02 2012
"Timon Gehr" wrote in message news:jns5du$64q$1 digitalmars.com... On 05/02/2012 08:58 PM, Mehrdad wrote:a*mutable state in an const method=O
May 02 2012
On 2012-05-02 20:58, Mehrdad wrote:Er, I guess I didn't say what I actually meant to say, my bad. x_x What I meant that you're assuming that derived classes won't need mutable state in an const method that they overrode.Yes, that would be the assumption. It's not possible without subverting the type system. It's like saying "I'm overriding this method but I want it to return an int instead of a string". It's part of the interface. -- /Jacob Carlborg
May 02 2012
On 05/02/2012 08:48 AM, Mehrdad wrote:In the world of OOP,interface Readonly{ auto read(){ ... } } class Mutable{ auto read(){ ... } void write(int x){ ... } Readonly getReadonly(){ ... } } private class Adapter: Readonly{ Mutable field; auto read(){ return field.read(); } }when would "guarantee"ing (so to speak) 'physical' const-ness ever be handy?Concurrency?Wouldn't "physical" const-ness be an implementation detail of the object, and therefore,If it is, don't use the const qualifier.impossible to determine by the user of the object?It is possible because 'const' is part of the method interface.
May 02 2012
On Wednesday, 2 May 2012 at 06:10:04 UTC, Mehrdad wrote:I don't think you answered my question. What I said (or meant to ask) was this: - OK, FINE, let's say I don't know what D's const() means, then. Maybe it isn't suitable for what I need. I just want to know: _How do you specify that the function process1() won't modify its 'student' parameter_?If you want just to specify that (and not ask the compiler to check for you), documentation will do.Your response was this: - "Does const(Student) mean that it shouldn't write to the server?" To which my answer is: - I don't know, you tell me. (?) _Should_ I use 'const' to specify this? Or should I use something else?I can't tell you what you want. You have to tell me. But no, you shouldn't use const to specify this. const means you're object won't be changed by you. Here's a decision tree for if you can use const and immutable Will the object (including all of its fields) ever change? no -> immutable (END) yes -> Will the object (including all of its fields) be changed by you? no -> const (END) yes -> No qualifier That's _any_ kind of modification of state. If you change state, you can't use immutable/const (nor would you really want to).The problem with your answer is that it implies D DOESN'T SUPPORT O.O.P. with const!!! If D supported OOP, then why the heck would process1() know (or _care_) how Student works internally? Why (or how) should it know (or care) that Student uses a database connection?When you say "const(Student)" you're saying that whatever Student does, you want to know whatever you do won't change its internal state. I'm not sure you're using OOP properly. If you want to use OOP to solve the problem, my suggestion is closer to OOP than using const. If you don't want to talk about database connections and everything (i.e., you don't want to use D's outrageously nice templates), you can just have it be an interface instead. In fact an interface would probably be more appropriate. interface ROStudent { getName(); getAddress(); ... } process1(ROStudent student); Effectively, though, it's the same sort of thing. D's const/immutable require a different way of thinking of it. What are they useful for? Consider a multithreaded program where some threads depend on information that others have. They need the information but they won't change state (which makes it much safer/faster to work with because locking isn't as necessary ... with immutable locking is actually silly). That's what const/immutable is for. In C++, you'd be foolish to not have locks set up even for const variables because they can change at any time for any reason. It's hardly a guarantee and it's so common to violate (look at yourself, for instance) that it means _nothing_. I liked how it was described as a glorified comment, because that's precisely how I think of it.Wasn't that information an implementation detail? All process1() needs to know is the *PUBLIC INTERFACE* of Student. And all it cares to say is: "According to the public interface of Student, none of the methods I will use will modify the object." process1() has NO IDEA what goes on internally! It has NO IDEA that Student uses a database connection. Maybe it's a network connnection instead. Or maybe it's being unit-tested, and it's actually a dummy object, with no relation to the outside world. Or maybe it's a wrapper/proxy for another object. The point is: _why should the code for process1() depend on this_?!If that's what you want, then that's precisely what D does. "According to the public interface of Student, none of the methods I will use will modify the object." As opposed to C++'s method of "According to the public interface of student, these methods have comments that say that they say they won't change the object, but I can't tell if it does or not." Again, using an interface ROStudent/StudentReader/etc is really your "OOP solution".So, I'll ask my question again: How should process1() tell the outside world that it will not be asking its parameter to manipulate itself, WITHOUT breaking the abstraction barrier and 'peeking' into the private world of Student?I'll respond again: It depends on what you want. If you won't be changing the object, then const/immutable does that. If you will be changing the object, but you want guarantees that it won't write to some external source/data structure, you'll have to come up with the more precise way you want to define that. I gave you two potential approaches. ---- Really though, the C++ way is a complete and total minefield in this regard anyway. Consider Student defined like so: Student { name, id, address, phone_number; connection; getName() const { if(!name) { // cast away const thisnonconst.name = connection.get("name"); } return name; } writeName() { connection.set("name", name); } } Now this follows one particular understanding of const (the one I think you're getting at). However, imagine another person comes along and sees that "writeName" doesn't write to Student's state. Helpfully, he adds const to the end of writeName and breaks your type system. In fact, it's actually _easier_ to violate "that kind of const" than it is to use it, because your "getName" method has to do fancy casting away of const. In fact, D's way, if you specify that Student can only use a ReadOnlyConnection (or if you use ROStudent/StudentReader interface), then the compiler will guarantee that it has a ReadOnlyConnection. If ReadOnlyConnection doesn't write to the database, then that Student is _guaranteed_ to not write to the database. If you want the compiler to check that, the D offers the ability to. C++, on the other hand (using const), can barely suggest that possibility, since it's so easy to violate.
May 02 2012
On 2012-05-02 13:41, Chris Cain wrote:If that's what you want, then that's precisely what D does. "According to the public interface of Student, none of the methods I will use will modify the object." As opposed to C++'s method of "According to the public interface of student, these methods have comments that say that they say they won't change the object, but I can't tell if it does or not."For D, it's not limited to the public interface. The rules apply to ALL methods and fields. -- /Jacob Carlborg
May 02 2012
On Wednesday, 2 May 2012 at 12:19:35 UTC, Jacob Carlborg wrote:For D, it's not limited to the public interface. The rules apply to ALL methods and fields.Indeed. I may have forgot to add "in any way shape form or fashion" to that particular line, but it's important to node that const means it won't change anything about the object (which includes fields). Which makes much more sense than "logical const" which has completely arbitrary meaning in almost every case I've seen.
May 02 2012
"Chris Cain" wrote in message news:wyqyigxytaqwwmhfhlej forum.dlang.org...If you want just to specify that (and not ask the compiler to check for you), documentation will do.Heh, if only the world was so ideal...That's _any_ kind of modification of state. If you change state, you can't use immutable/const (nor would you really want to).Okay...D's const/immutable require a different way of thinking of it. What are they useful for? Consider a multithreaded program where some threads depend on information that others have.I guess this answers my question then: const/immutable are useful for multithreading. So it means, essentially, they're pretty useless for Objects, but only useful for structs. Why? Objects have **behavior** (not just data), and depending on the _internal details_ of that behavior would be depending on an implementation detail. Sure, the method writer can call the method 'const', but he really has no idea whether someone will override it down the road and get struck by lightning because of that. What D's const seems to be is for *data*, pure and simple (no pun intended). Using it for behavior would be trying to predict the future implementations, and that is pretty damn hard. Because of that, I think we disallow direct instantiation of const() or immutable() Objects altogether, because they're pretty useless.In C++, you'd be foolish to not have locks set up even for const variables because they can change at any time for any reason.Huh? Not if they're instance variables. It's kind of silly to lock against something so you can modify instance variables.It's hardly a guarantee and it's so common to violate (look at yourself, for instance) that it means _nothing_.]I don't think I ever violated C++'s 'const'... (?)I liked how it was described as a glorified comment, because that's precisely how I think of it.More like, a **CHECKED** comment. Big difference.
May 02 2012
On Wednesday, 2 May 2012 at 15:35:15 UTC, Mehrdad wrote:I guess this answers my question then: const/immutable are useful for multithreading.Indeed, const/immutable makes multithreading much better. It provides restrictions which make it much easier to think about problems. If I pass a const reference into a function, I can be guaranteed it won't be changed by that function.So it means, essentially, they're pretty useless for Objects, but only useful for structs. Why? Objects have **behavior** (not just data) ...Structs in D have behavior as well. In fact, I've found most things I might be forced to use objects/classes in most other languages can be easily done with D's structs and all of the behavior and state is encapsulated nicely. Combine that with templates and you've got some ridiculous power and efficiency.Sure, the method writer can call the method 'const', but he really has no idea whether someone will override it down the road and get struck by lightning because of that.If the method writer has the method be const, then he's said that the contract of that method is that it doesn't change the object's state. This is no different than having a method that takes a string and complaining that the method writer has no idea whether someone will want to override it later and want the method to take an int. You can overload the method to take an int. And you can overload the method later down the line so that you have an implementation which does mutate state and isn't const. Although, you certainly need to have a version which doesn't mutate state available still. But that's just part of the contract. If you're writing a method and you've decided "I want whoever uses this method to not cause any mutation of state when they call this method" (maybe so there's a guarantee of consistency in execution speed between calls or something), then you mark it const. Otherwise, it's not really a const method.Because of that, I think we disallow direct instantiation of const() or immutable() Objects altogether, because they're pretty useless.Why? There's no reason to throw the baby out with the bath water. There's plenty of need for inheritance for immutable/const representation of data. Just because we can't use it for objects whose whole life is caching (which is a clear subset of OOP usage) doesn't mean it's useless.Huh? Not if they're instance variables. It's kind of silly to lock against something so you can modify instance variables.Depends. If you're just accessing the instance method directly (and it's an atomic operation), you can get away with not locking. However, If you have a C++ const object and you call a getter method on it in a context where someone else may also be calling a getter method on it, you must lock it. By your own admission, you can't depend on implementation of the object. Ergo, the getter methods may be mutating state (you can't assume it's not), and you can't tell whether or not they are because const in C++ doesn't say whether it will mutate state. Of course, if you don't depend on the correctness of the method you're free to not lock... In D, if I'm sure these const objects aren't being modified elsewhere, I can absolutely get away with not locking when I call getter methods. If it's an immutable object, it doesn't even cross my mind whether I need to lock it or not. You don't have that kind of peace of mind in C++.I don't think I ever violated C++'s 'const'... (?)Well, seeing as C++ const apparently has no meaning ... it's rather hard to "violate" it, per say. Honestly, the time my professors taught me about C++'s const, they said it was for methods that don't modify state. Evidently that wasn't the case, but I never got into the mindset that const didn't mean something that doesn't modify state... so it's rather hard for me to think of a reason to use const for this. Const to me has always meant "don't mutate state." If you just want a view of the object where you only use getters, that's really what D's interfaces are for.More like, a **CHECKED** comment. Big difference.Not exactly. It's not "checked" because const objects can be changed by anyone at anytime easily and it's common and the compiler doesn't care. It doesn't mean anything. Again, what I think you actually want is an interface of the getters, not const. There's no sense in having both do the same thing when we can actually use const to mean an object that you won't mutate. =-=-=-= In any case, would the Student-getter style interface method work for this use case or not? interface IStudentRO { string getName(); string getAddress(); real getGPA(); ... } class Student : IStudentRO { IConnection conn; string name; ... string getName() { if(!name) name = conn.get("name"); return name; } ... void setName(string newName) { name = newName; conn.set("name", newName); } ... } void process1(IStudentRO student); // Compile-time checking ... can only use getters.
May 02 2012
Okay thanks for the replies, that clears up a bunch of things for me. Regarding the last part: Yes, it 'works'. The only trouble is that you can never set the interface methods to be 'const', because any sort of indirection can screw you over later. This is why const is less useful than it could be.
May 02 2012
On Wednesday, 2 May 2012 at 20:30:10 UTC, Mehrdad wrote:Okay thanks for the replies, that clears up a bunch of things for me.No problem, I'm glad it's helping.Regarding the last part: Yes, it 'works'. The only trouble is that you can never set the interface methods to be 'const', because any sort of indirection can screw you over later. This is why const is less useful than it could be.You can set interface methods to be const, but you have to understand that you're saying that method can't mutate state. There's also nothing stopping you from specifying that both const and non-const methods are available, but it would increase the workload of people down the line especially if the const and non-const version differ significantly. I think D's const is as useful as it needs/can be. That said, I can understand why you might feel that way until you start using const/immutable correctly. Once you see how it improves your conceptual model of approaching algorithms, I won't be surprised if you end up as annoyed with C++'s const as I am. It's kind of why I pointed out the whole thing where you should lock your object even when you're just calling getters on a const object. It's actually deceptively incorrect to not do that in C++.
May 02 2012
On Wednesday, May 02, 2012 13:30:11 Mehrdad wrote:Okay thanks for the replies, that clears up a bunch of things for me. Regarding the last part: Yes, it 'works'. The only trouble is that you can never set the interface methods to be 'const', because any sort of indirection can screw you over later. This is why const is less useful than it could be.You _can_ make the interface methods const if you're willing to deal with that restriction in everything that implements it. Sometimes that's reasonable. Sometimes it's not. The simple truth of the matter is that you can't just const in D as much as you would in C++, simply because it's more restrictive. It's more powerful thanks to its greater guarantees, but that comes at a cost. So, programmers are going to have to learn when using const in D makes sense and when it doesn't, even if they're experts at in C++. And a lot of it depends on what you're doing (e.g. I pretty much never lazy load anything, so const would never cause me problems due to disallowing lazy loading, but for someone who needs lazy loading for performance, const make be unacceptable in many cases). - Jonathan M Davis
May 02 2012
Could someone mention a case where it is __necessary__ to cast away const()? How about immutable()? How about shared()?
May 02 2012
On 02-05-2012 17:58, Mehrdad wrote:Could someone mention a case where it is __necessary__ to cast away const()? How about immutable()? How about shared()?shared? Almost always in any non-trivial application. shared is only useful if you're dealing with templatized functions that can actually handle it, which is not the case as often as one would like.-- - Alex
May 02 2012
On Wednesday, 2 May 2012 at 16:52:33 UTC, Alex Rønne Petersen wrote:shared? Almost always in any non-trivial application. shared is only useful if you're dealing with templatized functions that can actually handle it, which is not the case as often as one would like.Additionally, shared is currently little more than a marker for non-TLS data. David
May 02 2012
On Wed, 02 May 2012 12:59:34 -0400, David Nadlinger <see klickverbot.at>= = wrote:On Wednesday, 2 May 2012 at 16:52:33 UTC, Alex R=C3=B8nne Petersen wro=te:=shared? Almost always in any non-trivial application. shared is only ==useful if you're dealing with templatized functions that can actually=S =handle it, which is not the case as often as one would like.Additionally, shared is currently little more than a marker for non-TL=data.No, it's very important that it is a type constructor. For example, it = = makes weak-pure functions possible. I think there is a large piece of shared missing/undefined, and that is,= = how do I mark something shared as "temporarily local". I think Bartosz = = proposed something like "lent". We essentially need the equivalent of const for shared. Const unifies = immutable and mutable, we need something to unify shared and thread-loca= l. The problem is, something like this needs to be combined with = thread-locks. I wonder if some kind of ARC would be useful for = automatically unlocking the data. -Steve
May 03 2012
On Thursday, 3 May 2012 at 13:40:41 UTC, Steven Schveighoffer wrote:On Wed, 02 May 2012 12:59:34 -0400, David Nadlinger <see klickverbot.at> wrote:Yes, it is important indeed, at least if we want the type system to give guarantees about multi-threaded code. I just meant that in the current state, it provides little more than a way to make sure that shared data isn't accidentally passed around/used in places where synchronization isn't properly handled, because none of the fancier related ideas have actually been implemented so far. DavidAdditionally, shared is currently little more than a marker for non-TLS data.No, it's very important that it is a type constructor. For example, it makes weak-pure functions possible. I think there is a large piece of shared missing/undefined, and that is, how do I mark something shared as "temporarily local". I think Bartosz proposed something like "lent". We essentially need the equivalent of const for shared. Const unifies immutable and mutable, we need something to unify shared and thread-local. The problem is, something like this needs to be combined with thread-locks. I wonder if some kind of ARC would be useful for automatically unlocking the data.
May 03 2012
Hi folks, it was really good and productive discussion on const and immutability, special thanks for those (Jonatan,Steven,Cris,etc) who answered hard Mehrad's questions and clarified so important topics. Can I kindly ask you folks to update FAQ on the site to have ability for newcomers to see answers on Why const and immutable? Why the names const and immutable? How exactly is immutable related to multicores? Ok, I'm fine with immutable for safe data sharing among threads. But why do we need the uninformative const? Why are immutable strings favored in D 2.0? it are still open but I feel majority of it were answered here. "Attributes" page has no answered also "shared", "immutable", "inout" and "const"'s description is so uninformative taking into account how much details were discussed here. It will definitelly help and reduce amount of repeated noisy topics in major and learn forums. Great thanks, Oleg.
May 04 2012
Okay, that's for shared. What about const though?
May 02 2012
On Tuesday, May 01, 2012 21:05:11 Mehrdad wrote:Okay, so let's say you're right void process1(Student s) { .. } void process2(const(Student) s) { ... } and that what I guess what I REALLY want is to say, "process1() won't change the student's name, birthday, or any other attribute, but process2() will". How do you propose I make that guarantee known to the compiler/caller? Is that not the entire point of saying "const(Student)" in the first place?Yes. And if you mark it as const, then the compiler will guarantee it. But with something like lazy loading, the object _does_ change. In D, const actually guarantees that a const object will not be altered, whereas C++'s doesn't. If you have an object which you don't intend to _logically_ alter but which whose state may change due to lazy loading, then the compiler can't give you any real guarantees anyway without having a lazy-loading mechanism of some sort built into the language. In C++, the compiler can't use it to provide any real guarantees, because the programmer is free to violate const at any time via mutable or casting away const. Walter thinks that this makes C++'s const utterly useless (his focus is very much on what the compiler can and can't guarantee). Most of the rest of us disagree (it _does_ at least stop the programmer from inadvertently mutating a const variable directly), but with Walter's take on it, there's no way that he would have made D's const the same. And C++'s const just doesn't provide the kind of guarantees that D requires anyway, since the fact that a const object could actually be immutable makes it so that you can't safely cast away const to mutate anything, regardless of whether the compiler assumes that const objects never get altered. The lack of logical const in D can be very annoying for some use cases, but the fact that D's const is guaranteed then makes it much better for other use cases. It's a tradeoff. If all you want is something which indicates that you don't intend to alter the logical state of a variable (but you may alter its state in other ways - e.g. its cache), then D's compiler can't help you with that. Not even C++'s compiler really does much for that, since its const can be circumvented so easily. It just helps prevent you from doing it accidentally. - Jonathan M Davis
May 01 2012
In C++, the compiler can't use it to provide any real guarantees, because the programmer is free to violate const at any time via mutable or casting away const.Maybe I'm being stupid, but how is the case any different in D?
May 01 2012
On Tuesday, May 01, 2012 23:14:06 Mehrdad wrote:D's type system assumes that const cannot be altered. As such, the compiler is free to optimize or alter its code generation based on that fact and any code which _does_ alter a variable after casting away const is breaking the type system and risking bugs. As far as the compiler is concerned, you _cannot_ alter a const variable through that variable (though another one might - unlike immutable - meaning that there are times when the compiler can't know that a const variable hasn't been mutated; it often can, however, particularly within a single function). C++ specifically allows for const variables to be altered - either via mutable or casting away const. It _guarantees_ that it work to do so. And since it considers it completely legal to alter a const variable by either of those means, it _cannot_ assume that a const variable hasn't been altered except in very restricted circumstances. So, in general, the _only_ guarantee that C++'s const gives you is that you a const variable will not be altered save by casting away const or by one of its member variables being mutable. So, it all comes down to what the compiler is free to assume and therefore guarantee. With D's type system, it's free to assume that you can never alter a const variable, so it guarantees that and any attempt to subvert that breaks the type system and whatever guarantees it gives you, risking bugs. With C++'s type system, it assumes that const _can_ be mutated via casting or mutable and guarantees that that will work, whereas it then _cannot_ assume that a const variable doesn't get mutated. - Jonathan M DavisIn C++, the compiler can't use it to provide any real guarantees, because the programmer is free to violate const at any time via mutable or casting away const.Maybe I'm being stupid, but how is the case any different in D?
May 01 2012
You said: "In C++, the compiler can't use it to provide any real **guarantees**..." Now you're telling me D doesn't guarantee it either. And then go on telling me about how D uses const-ness to make assumptions and improve performance. Isn't your answer orthogonal to your claim? o.O Whether the compiler makes **GUARANTEES** about code is _irrelevant_ to whether or not the language uses 'const' to improve performance. You could very well have a language which **enforces** const-ness (i.e. doesn't prevent casting it away), but which _doesn't_ use it to improve performance. Or which does. The dot product of your answer with my question was zero. :(
May 01 2012
... i.e. doesn't prevent casting it away)Typo, I meant "DOES prevent"
May 01 2012
On Tuesday, May 01, 2012 23:44:30 Mehrdad wrote:You said: "In C++, the compiler can't use it to provide any real **guarantees**..." Now you're telling me D doesn't guarantee it either. And then go on telling me about how D uses const-ness to make assumptions and improve performance. Isn't your answer orthogonal to your claim? o.O Whether the compiler makes **GUARANTEES** about code is _irrelevant_ to whether or not the language uses 'const' to improve performance.No, what the compiler guarantees is _very_ relevant to using const for improving performance. The _only_ reason that the compiler can use const for any kind of optimizations is because it can guarantee that the variable won't change. If it can't guarantee that, then it _cannot_ use const for optimizations. How could it? What would there be to optimize? Optimizations with const are based on the fact that the variable didn't change, so if the compiler can't guarantee that, how could it make any optimizations based on that? And yes, D's type system _does_ guarantee that a const object doesn't change specifically because casting away const and mutating an object is _undefined_. It goes outside of the type system. You can do it, because D is a systems language, but the compiler is free to make optimizations based on the assumption that you will never mutate a const variable. So, the compiler _can_ make the guarantee that const isn't mutated, and if you do, you _will_ have bugs - just like if you did something screwy like casting an object no void* nad then casting it to a completely different type afterwards could cause bugs. You've thrown away the type system at that point, and it's up to the programmer to maintain the compiler's guarantee in such situations, or you'll get bugs, because the compiler relies on that guarantee. C++, on the other hand, _does_ define what happens when you cast away const and mutate a variable, so its type system has to assume that a const variable _can_ change and therefore can rarely use it for optimizations (i.e. only in cases where it can statically verify that you _didn't_ cast away const and that not mutable variables are involved - which is likely to be rather rather, since even just making a function call would defeat it, since C++ doesn't do cross-function optimizations like that). The difference here is in what the compiler considers to be defined or not and therefore what assumptions it's permitted to make. - Jonathan M Davis
May 02 2012
Whoa, what? So you're saying "X results in UB" means "Compiler guarantees X" ? By that philosophy, C and C++ are orders of magnitude better than D, given how many so-called "guarantees" they make about your code...
May 02 2012
On Wednesday, May 02, 2012 00:10:33 Mehrdad wrote:Whoa, what? So you're saying "X results in UB" means "Compiler guarantees X" ? By that philosophy, C and C++ are orders of magnitude better than D, given how many so-called "guarantees" they make about your code...I don't follow. The D compiler guarantees that as long as you don't cast away const, const will never be mutated. If you _do_ cast away const and then mutate the variable, you are doing something which is undefined. As it is undefined behavior, _anything_ could happen if you do it, and the compiler is free to assume that it will never happen. So, it effectively has a guarantee that a const variable will never be mutated (save by another, mutable reference to the same data). It then uses that guarantee for optimizations. To make it 100% iron-clan, casting away const would have to be illegal, but that would be unacceptable in a systems language - particularly when you want to be able to call C functions which may not be properly annotated. So instead, the compiler assumes that its guarantee holds in the case where you cast away const, and is still able to use it for optimizations. C++ _does_ define what happens when you cast away const and mutate a variable, so it guarantees that that behavior will be safe. In so doing however, it is then unable to assume that casting away const will not result in the variable being mutated and is therefore unable to use const for much in the way of optimizations. - Jonathan M Davis P.S. You can check out this stackoverflow question on the subject as well: http://stackoverflow.com/questions/4219600/logical-const-in-d
May 02 2012
You can replace "signed integer overflow" with pretty much whatever 'guarantee' in C that suits your fancy. See below. "Jonathan M Davis" wrote in message news:mailman.207.1335944070.24740.digitalmars-d puremagic.com...I don't follow.Let's see if this is better.The D compiler guarantees that as long as you don't cast away const, const will never be mutated.The C compiler guarantees that as long as signed integers don't overflow, their results will be correct.If you _do_ cast away const and then mutate the variable, you are doing something which is undefined.If you _do_ overflow signed integers, their results will be undefined.As it is undefined behavior, _anything_ could happen if you do it, and the compiler is free to assume that it will never happen.As it is undefined behavior, the result could be _anything_ if you do it, and the compiler is free to assume that it will never happen.So, it effectively has a guarantee that a const variable will never be mutated (save by another, mutable reference to the same data). It then uses that guarantee for optimizations.So, it effectively has a guarantee that a signed integer will never be overflown. It then uses that guarantee for optimizations.To make it 100% iron-clan, casting away const would have to be illegal, but that would be unacceptable in a systems language - particularly when you want to be able to call C functions which may not be properly annotated.To make it 100% iron-clan, signed overflow would have to be illegal, but that would be unacceptable in a systems language - particularly when you need to depend on system-dependent behavior.So instead, the compiler assumes that its guarantee holds in the case where you cast away const, and is still able to use it for optimizations.So instead, the compiler assumes that its guarantee holds in the case where overflow a signed integer, and is still able to use it for optimizations.C++ _does_ define what happens when you cast away const and mutate a variable, so it guarantees that that behavior will be safe.D _does_ define what happens when you overflow a signed integer, so it guarantees that that behavior will be safe.In so doing however, it is then unable to assume that casting away const will not result in the variable being mutated and is therefore unable to use const for much in the way of optimizations.In so doing however, it is then unable to assume that a signed integer will never be overflown, and is therefore unable to use signed integer overflow for much in the way of optimizations. - Jonathan M Davis Mehrdad
May 02 2012
On Wednesday, May 02, 2012 01:06:47 Mehrdad wrote:You can replace "signed integer overflow" with pretty much whatever 'guarantee' in C that suits your fancy. See below. "Jonathan M Davis" wrote in message news:mailman.207.1335944070.24740.digitalmars-d puremagic.com...All of that might hold if the compiler could actually make optimizations based on the knowledge that an integer wouldn't overflow. But what optimizations could it make? Regardless, I don't see how it's particularly relevant. Even if there were optimizations which C/C++ could make which D can't (which may or may not be the case), that wouldn't have any bearing on how D's const works. D's const and C/C++'s const have different goals and different pros and cons. D's const is physical const, whereas C/C++'s const is an attempt at logical const (though it isn't really logical const either, because it makes no guarantees that the logical state of the object remains the same - only that you don't directly mutate any const variables). - Jonathan M DavisI don't follow.Let's see if this is better.The D compiler guarantees that as long as you don't cast away const, const will never be mutated.The C compiler guarantees that as long as signed integers don't overflow, their results will be correct.If you _do_ cast away const and then mutate the variable, you are doing something which is undefined.If you _do_ overflow signed integers, their results will be undefined.As it is undefined behavior, _anything_ could happen if you do it, and the compiler is free to assume that it will never happen.As it is undefined behavior, the result could be _anything_ if you do it, and the compiler is free to assume that it will never happen.So, it effectively has a guarantee that a const variable will never be mutated (save by another, mutable reference to the same data). It then uses that guarantee for optimizations.So, it effectively has a guarantee that a signed integer will never be overflown. It then uses that guarantee for optimizations.To make it 100% iron-clan, casting away const would have to be illegal, but that would be unacceptable in a systems language - particularly when you want to be able to call C functions which may not be properly annotated.To make it 100% iron-clan, signed overflow would have to be illegal, but that would be unacceptable in a systems language - particularly when you need to depend on system-dependent behavior.So instead, the compiler assumes that its guarantee holds in the case where you cast away const, and is still able to use it for optimizations.So instead, the compiler assumes that its guarantee holds in the case where overflow a signed integer, and is still able to use it for optimizations.C++ _does_ define what happens when you cast away const and mutate a variable, so it guarantees that that behavior will be safe.D _does_ define what happens when you overflow a signed integer, so it guarantees that that behavior will be safe.In so doing however, it is then unable to assume that casting away const will not result in the variable being mutated and is therefore unable to use const for much in the way of optimizations.In so doing however, it is then unable to assume that a signed integer will never be overflown, and is therefore unable to use signed integer overflow for much in the way of optimizations.
May 02 2012
On 05/02/2012 10:26 AM, Jonathan M Davis wrote:All of that might hold if the compiler could actually make optimizations based on the knowledge that an integer wouldn't overflow. But what optimizations could it make?x+1 > x ==> true
May 02 2012
All of that might hold if the compiler could actually make optimizations based on the knowledge that an integer wouldn't overflow. But what optimizations could it make?Look for "signed integer overflow" here. http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html
May 02 2012
On 5/2/12 3:10 AM, Mehrdad wrote:Whoa, what? So you're saying "X results in UB" means "Compiler guarantees X" ? By that philosophy, C and C++ are orders of magnitude better than D, given how many so-called "guarantees" they make about your code...Casting away const should be statically disallowed in safe code. Andrei
May 02 2012
On Wed, May 02, 2012 at 09:57:58AM -0400, Andrei Alexandrescu wrote: [...]Casting away const should be statically disallowed in safe code.[...] Doesn't it already? T -- Everybody talks about it, but nobody does anything about it! -- Mark Twain
May 02 2012
H. S. Teoh:Doesn't it already?Right, this: class Foo {} void main() safe { const f1 = new Foo; auto f2 = cast(Foo)f1; } Gives: test.d(5): Error: cast from const(Foo) to test.Foo not allowed in safe code Bye, bearophile
May 02 2012
On Wed, May 02, 2012 at 05:16:26PM +0200, bearophile wrote:H. S. Teoh:[...] Thought so. One area where I'd like const to be improved, though, is a way to indicate that const-ness depends on the arguments, specifically, on the behaviour of a delegate argument. For non-delegate arguments we have inout (which is also its own minefield, but anyway), but it sucks that opApply can never be marked safe, const, or pure because there's no guarantee at all what the delegate will do. (Forcing the delegate to be any of the above is not really an option, because what if you *needed* to call opApply with an impure delegate?) This on its own is no big deal, but the thing is, const, pure, and safe are all "viral". All it takes is for a single nested function to call opApply somewhere, and suddenly the whole function call chain can no longer be marked const/pure/ safe. T -- Let X be the set not defined by this sentence...Doesn't it already?Right, this: class Foo {} void main() safe { const f1 = new Foo; auto f2 = cast(Foo)f1; } Gives: test.d(5): Error: cast from const(Foo) to test.Foo not allowed in safe code
May 02 2012
H. S. Teoh:One area where I'd like const to be improved, though, is a way to indicate that const-ness depends on the arguments, specifically, on the behaviour of a delegate argument.There was a long discussion about this, with proposals like pure(compile_time_predicate) to define a conditional purity. But Walter decided for a simpler solutions, where the purity and "non-throwness" of templates is inferred. Bye, bearophile
May 02 2012
On Wednesday, 2 May 2012 at 06:44:30 UTC, Mehrdad wrote:Whether the compiler makes **GUARANTEES** about code is _irrelevant_ to whether or not the language uses 'const' to improve performance. You could very well have a language which **enforces** const-ness (i.e. doesn't prevent casting it away), but which _doesn't_ use it to improve performance. Or which does.Yes, pure functional languages do provide this kind of guarantee, by simply forbidding immutability at the cost of copying objects. I agree D's solution isn't "pure" as it must allow mutability, and I suspect it can't be, unless it starts copying objects all over the place. That means that when you use the immutable keyword, you *really* mean it and you *must* think that way. If you want to mutate your object, you have to copy it to another object. Thats how strings are designed. For me, your example doesn't prove that the tool is broken, it proves that you don't know how to use immutability. It's really another paradigm, which has its uses, in particular in concurrent programming.
May 02 2012
Mehrdad:"Lazy-loading" and caching aren't exactly obscure or rarely-used concepts -- which seems to be the way they are portrayed here. They are *bound* to be used in any nontrivial program.Right. - - - - - - - - - - Andrei Alexandrescu:Casting away const should be statically disallowed in safe code.I agree. Bye, bearophile
May 02 2012
Also, I think you didn't notice the other problem. The other problem was with IConnection, whose send() wasn't 'const', which also gives you an error due to the transitivity of const (which I've also claimed is broken). And how could it? It's just a connection, not a server, so it doesn't parse the input... and it probably changes the state of the connection, since connections often have internal buffers they need to modify (if not prioritization, etc.). So, what is D's solution for _that_ problem?
May 01 2012
On Tue, 01 May 2012 23:30:27 -0400, Mehrdad <wfunction hotmail.com> wrote:Also, I think you didn't notice the other problem. The other problem was with IConnection, whose send() wasn't 'const', which also gives you an error due to the transitivity of const (which I've also claimed is broken). And how could it? It's just a connection, not a server, so it doesn't parse the input... and it probably changes the state of the connection, since connections often have internal buffers they need to modify (if not prioritization, etc.). So, what is D's solution for _that_ problem?There are two solutions, both are horrible. 1. Cast. Coder beware. 2. Store conn outside the instance (i.e. in a hash lookup). Horrible. There is a possible 3rd solution. Don't use immutable/const, instead use information hiding. In other words, Your class is already technically "immutable", since name will never mutate beyond the first setting. This requires coder discipline, and has no help from the compiler. But many languages that *don't* have const/immutable do well with this pattern (think Java strings). I have periodically mulled the idea of making a library-based solution for logical const. I think it would work, but you would have to be extremely cautious, and you lose some compiler guarantees for it. The most difficult part to deal with is how to prevent concurrent access, when an immutable object is always implicitly sharable. I have another idea which gets around the problem in a different way. I think it's essential to bringing arbitrary ranges in line with current array features. When I have some time to flesh it out, I'll propose it. I think we absolutely can't be finished with ranges until this is implemented. -Steve
May 02 2012
"Steven Schveighoffer" wrote in message news:op.wdokh6vteav7ka localhost.localdomain...There are two solutions, both are horrible.That's what scares me lolThere is a possible 3rd solution. Don't use immutable/const, instead use information hiding. In other words, Your class is already technically "immutable", since name will never mutate beyond the first setting. This requires coder discipline, and has no help from the compiler.Yup.But many languages that *don't* have const/immutable do well with this pattern (think Java strings).Java strings are pretty poor for performance though. :\ You shouldn't be forced to choose between O(1) performance (versus O(n)) and the correctness of your program.I have periodically mulled the idea of making a library-based solution for logical const. I think it would work, but you would have to be extremely cautious, and you lose some compiler guarantees for it. The most difficult part to deal with is how to prevent concurrent access, when an immutable object is always implicitly sharable.Yeah, 'shared' also suffers from similar limitations...I have another idea which gets around the problem in a different way. I think it's essential to bringing arbitrary ranges in line with current array features. When I have some time to flesh it out, I'll propose it. I think we absolutely can't be finished with ranges until this is implemented.Okay nice. I also think this is 100% relevant to the range issue, so I don't think we will see a true fix until this problem is solved.
May 02 2012
On Wednesday, 2 May 2012 at 15:21:31 UTC, Mehrdad wrote:"Steven Schveighoffer" wrote in message news:op.wdokh6vteav7ka localhost.localdomain...This is a false choice. The performance of Java strings (BTW, which I don't think is that bad) is not tied to whether they are immutable or not. -SteveBut many languages that *don't* have const/immutable do well with this pattern (think Java strings).Java strings are pretty poor for performance though. :\ You shouldn't be forced to choose between O(1) performance (versus O(n)) and the correctness of your program.
May 02 2012
This is a false choice. The performance of Java strings (BTW, which I don't think is that bad) is not tied to whether they are immutable or not.Oh yes, I think you're right. If I remember, someone told me Java strings don't re-copy data when you substring; I forgot about that.
May 02 2012
(which, I should clarify, was what implicitly came to my mind as an example of immutable strings)
May 02 2012
Mehrdad:Notice that there are two const-related issues in the code that are literally *unsolvable* (unless you avoid const/immutable entirely, or unless you cast() -- but then your entire code would be filled with dangerous const casts...).Casting away casts is rather dangerous in D (more than in C++), I kind of never do it. So just don't use const/immutable in such cases. Bye, bearophile
May 01 2012