Re: length's type.
On Tuesday, 13 February 2024 at 23:57:12 UTC, Ivan Kazmenko wrote: I'd like to note that even C++20 onwards has `.ssize`, which is signed size. I do use lengths in arithmetic sometimes, and that leads to silent bugs currently. On the other hand, since going from 16 bits to 32 and then 64, in my user-side programs, I had a flat zero bugs because some length was 2^{31} or greater -- but at the same time not 2^{32} or greater. So, in D, I usually `to!int` or `to!long` them anyway. Or cast in performance-critical places. Another perspective. Imagine a different perfect world, where programmers just had 64-bit integers and 64-bit address space, everywhere, from the start. A clean slate, engineers and programmers designing their first hardware and languages, but with such sizes already feasible. Kinda weird, but bear with me a bit. Now, imagine someone proposing to make sizes unsigned. Wouldn't that be a strange thing to do? The benefit of having a universal arithmetic type for everything, from the ground up -- instead of two competing types producing bugs at glue points -- seems to far outweigh any potential gains. Unsigned integers could have their small place, too, for bit masks and microoptimizations and whatnot, but why sizes? The few applications that really benefit from sizes of [2^{63}..2^{64}) would be the most odd ones, deserving some workarounds. Right now though, we just have to deal with the legacy, in software, hardware, and mind -- and with the fact that quite some environments are not 64-bit. Ivan Kazmenko. Personally, I don't have a problem with .length being unsigned. How do you have a negative length? My problem is that the language doesn't correctly compare signed and unsigned. Earlier in the thread, people mentioned NOT mentioning other languages but I just learned that Carbon correctly compares signed and unsigned ints. cheers
Re: length's type.
On Friday, 9 February 2024 at 11:00:09 UTC, thinkunix wrote: First off I, I am just a beginner with D. I joined this list to try to learn more about the language not to but heads with experts. I'm sorry if you took my response that way. Hi thinkunix, I did interpret your post as critical. Sorry if it wasn't intended to be and my reply had a little too much heat. I still think my reply was at least accurate, so replies below. My post was merely to show how, with my rudimentary knowledge, I could get the loop to execute 4 times, which appeared (to me) to be the intent of your code. Thank you for the exercise. I learned more about the D type system. I said I would not write code like that because: * why start at -1 if array indexes start at 0? The program that I was writing was most elegant doing that. Obviously I wasn't doing something as simple as the example. The post is simply to highlight the issue. Unfortunately I can't find the examples now. The code has been altered so grepping isn't finding it and, since AoC has 25 days, I'm not sure which ones it was. It /might/ have been this, where 'where_to_start' is signed and can be negative. It's a weird index of indexes thing, and quite unconventional. // Try it in the remaining groups. for (auto i = where_to_start; i < num_ss.length; ++i) * why use auto which made the type different than what .length is? Google "almost always auto" for why you should prefer it - don't miss Herb Sutter's post - but as someone else pointed out, it's no better with 'int' *or* 'ulong'. The "best" solution is to cast the returned length to long. This makes it work and, unless you're counting the atoms in the universe, should be sufficient on a reasonable machine. This is why I brought up the example. zjh was lamenting having to cast, as am I, much less think this hard about it. You provided no context, or comment indicated what you were trying to achieve by starting with -1. Clearly I didn't understand your intent. I wasn't asking a question. I know how to code this in D and I made it work. My post was to highlight the completely unnecessary need to cast. What happens when there's a more reasonable example? What happens when you need to compare 2 library function results when one is signed and the other not? Would you even know that you had to cast one? Or would you just get strange results and not know why? The issue exists completely outside of my example. Since you sound new, I'll mention that, yes, what I'm proposing can be a hair slower. But so what? 99 of a 100 programs won't notice and, if it does, your profiler will tell you where, you add the cast (or you add it ahead time, since that's what we have to do now), done. I understand that it is almost certainly too late for D but the world seems ready for an alternative to C++ and lots of languages are coming. This is just part of that discussion, right?
Re: length's type.
Arafel, You're certainly correct. Priorities change. It used to be thought that backwards compatibility was the way to attract developers. But today the keyword is "safety". Apparently 2022 was the year of the C++ successor. Some features of D were mentioned in the discussion but D as a candidate was not. Maybe it's time to re-visit the priorities. Anyways, my post was simply to highlight the issue. On Thursday, 8 February 2024 at 15:26:16 UTC, kdevel wrote: Elegant and correct is this version: ```d import std.stdio; int main() { char[] something = ['a', 'b', 'c']; writeln("len: ", something.length); writeln("typeid(something.length): ", typeid(something.length)); writeln ("i: -1"); foreach (i, _; something) writeln("i: ", i); return 0; } ``` But it is still a bit too "clever" in the `foreach` statement due to the unused variable `_`. It has a bigger problem. What happens when I change the code in the loop but forget to change the code outside the loop? This is why people complain about Python's lack of a do/while loop. So no, not elegant. Additionally, it doesn't address the issue. It still requires me to both realize the issue with comparing an int to length's mystery type, as well as to fix it for the compiler. (And it's beside the fact that the start value could just as easily be an int (parameter for example) that could take a negative value. This was the case for one of my cases.)
Re: length's type.
On Thursday, 8 February 2024 at 08:23:12 UTC, thinkunix wrote: I would never write code like this. By all means, please share with us how you would have written that just as elegantly but "correct". It would also break if the array 'something' had more than int.max elements. Then don't cast it to an int. First of all, why didn't you cast it to a long? Second, why doesn't the language do this correctly so I don't have to cast it at all? If I explicitly use checkedint, it does, but I don't want to write Checked!(int, ProperCompare) all over the place. (Yes, I know I can alias it.) I'm asking, why is the default C compatibility (of all things) rather than "safety that I can override if I need to make it faster" ?
Re: length's type.
On Wednesday, 7 February 2024 at 20:13:40 UTC, Gary Chike wrote: On Wednesday, 7 February 2024 at 20:08:24 UTC, Gary Chike wrote: On Wednesday, 7 February 2024 at 19:32:56 UTC, Gary Chike wrote: double c = (double)sumArray(a, aLength) / aLength; If I don't cast explicitly: `double c = sumArray(a, aLength) / aLength;` then I will get a similar result as the D code: `Average: 9223372036854773760.00` I don't think it's productive to compare the behavior to C. C is now 50 years old. One would hope that D has learned a few things in that time. How many times does the following loop print? I ran into this twice doing the AoC exercises. It would be nice if it Just Worked. ``` import std.stdio; int main() { char[] something = ['a', 'b', 'c']; for (auto i = -1; i < something.length; ++i) writeln("less than"); return 0; } ```
Re: Changing behavior of associative array
On Saturday, 16 December 2023 at 22:44:16 UTC, Dennis wrote: That's because `m[f] = 1` initializes the associative array to something non-null. If you pass a `null` AA to a function which adds things, the caller will still have a null pointers. You can initialize a non-null empty AA like this: ```D uint[Foo] m = new uint[Foo]; ``` Then, `m` can be passed by value and you can make additions or removals which the caller sees, unless you assign a new AA in the function. Thanks Dennis. I'll have to tweak my mental model of what's happening. I was picturing a std::map-like thing that was passed by reference, but instead it seems like 'm' is a pointer to a std::map, that is initialized on use if null. (I think it's that latter part that gives the illusion of being already initialized.)
Changing behavior of associative array
Perhaps someone can help solve this mystery. I have a sample program that adds structs to an associative array (as keys) in a subroutine. The program passes the array by reference as expected (below). My real program does the same thing and yet the associative array is passed by value. Thus anything added in the subroutine is lost. I've added a TON of writeln's to confirm this. The *instant* I made the parameter "ref", the real program starts acting as expected, and the print-outs show the correct results. Does anyone know why the behavior would change? Has anyone seen anything like this? I even tried importing the same libraries into both programs. The only remaining difference is that the real function has several more arguments (7), but I would be shocked if D changed its behavior because of that. thx ``` import std.stdio; struct Foo { uint a, b, c; } void add_one_and_recurse( uint[Foo] m, int x) { if (!x) return; foreach (k,v; m) // Prints the 1 from main() writeln(k, ", ", v); // as expected. add_one_and_recurse(m, x-1); Foo f = {4, 5, x}; m[f] = 7; } int main() { uint[Foo] m; Foo f = {1, 2, 3}; m[f] = 1; add_one_and_recurse(m, 3); foreach (k,v; m) // Prints 4 items writeln(k, ", ", v); // as expected. return 0; } ```
Re: Permutations of array (slice) ?
On Wednesday, 13 December 2023 at 20:28:11 UTC, Nick Treleaven wrote: This code works for me. (The last 20+ lines are `b`): ```d import std; void main() { char[] as = replicate(['a'], 5); as[0] = 'b'; foreach (perm; as.byChar.permutations) writeln(perm); } ``` Try it on https://run.dlang.io/. Maybe there's some issue on your machine? Also if you use `string as = "abcde";` you can see the permutations better. Revisiting this, I see that it works now. I suspect that I had writeln(as); Nevertheless, nextPermutation() does what I really want, which is: (given as[4] = 'b') b aaaba aabaa abaaa b
Re: Permutations of array (slice) ?
On Tuesday, 12 December 2023 at 15:20:23 UTC, Paul Backus wrote: Because of autodecoding [1], slices of char and wchar are treated by Phobos as bidirectional ranges of dchar. (They are not random-access ranges because UTF-8 and UTF-16 are variable-length encodings.) To work around this, use std.utf.byChar: import std.utf: byChar; foreach (perm; as.byChar.permutations) writeln(perm); [1] http://ddili.org/ders/d.en/ranges.html#ix_ranges.decoding,%20automatic ah, many thanks, I never would have figured that out. But unfortunately, the code shown now prints 120 lines of: b 120 being suspiciously equal to 5!. The documentation[2] seems to imply that this should be: b abaaa ... When I replace the char[] with int[] [2, 1, 1, 1, 1], it produces 120 lines of random arrangements of the input, but mostly just repeating the input. Clearly it's doing permutations of a separate array of [0, 1, 2, 3, 4], and then replacing each index with the element at that index (or the equivalent) but it's not even doing that right. I'll play with nextPermutation. I suspect it would have to be implemented the way that I'm expecting. [2] https://dlang.org/library/std/algorithm/iteration/permutations.html
Permutations of array (slice) ?
Could I get some help with a trivial question, I'm sure? How do pass a slice as a range? This code: import std.algorithm; // permutations import std.range; // replicate import std.stdio; // writeln int main() { char[] as = replicate(['a'], 5); as[0] = 'b'; foreach (perm; as.permutations) writeln(perm); } appears to complain (actual error below) that 'as' is not a range. Using the slice trick "as[]" from: https://forum.dlang.org/thread/oxohqaicmpteesugs...@forum.dlang.org doesn't help either. Something obvious I'm missing, I hope. perm.d:8:26: error: none of the overloads of template ‘std.algorithm.iteration.permutations’ are callable using argument types ‘!()(char[])’ 8 | foreach (perm; as.permutations) | ^ /usr/lib/gcc/x86_64-linux-gnu/13/include/d/std/algorithm/iteration.d:7919:20: note: Candidate is: ‘permutations(Range)(Range r)’ with `Range = char[]` must satisfy the following constraint: ` isRandomAccessRange!Range` 7919 | Permutations!Range permutations(Range)(Range r) |^
Re: Find in assoc array then iterate
ah, I knew that I could iterate over byKey, but I didn't know that it was a tangible thing, that you could hold in your hand. This should be fine, and I'll use your template trick for passing it to a function. Thanks Paul and Ali!
Re: Find in assoc array then iterate
Siarhei, Thanks for this possibility. I didn't know D had "pointers" like this. Unfortunately, it appears that you can't pick up where you left off with that pointer? You have to re-scan forward? bachmeier, I didn't reply to Steven's question; I replied to his claim that there was no value. I found his response insulting and patronizing, and has no place on a public forum helping evangelize D. See Siarhei's response for a better way. Siarhei, I'm attempting to do something like a DFS down a fixed list of strings. It's doing something like generating permutations of the strings, however the conditions for the search are fairly complicated, and of course I'm trying to do as much "early exit" as possible. That is, I can prune branches just seeing the first or second word. IOW, if D could, for example, "iterate through all 3 word combinations in this list", it would be useless to this problem (or at least *very* expensive.) OTOH, a forward iterator (i.e. copyable but does not need to go backwards) solves the problem elegantly and efficiently. Go-lang supports it too: package main import "fmt" import "reflect" func main() { m := make(map[string]int) m["key1"] = 7 m["key2"] = 8 iter := reflect.ValueOf(m).MapRange() // Advance iterator iter.Next() // Preserve position iter2 := *iter // Exhaust first iterator for iter.Next() { fmt.Println("iter: ", iter.Key()) } // What does second iterator see? for iter2.Next() { fmt.Println("iter2: ", iter2.Key()) } } will correctly print: iter: key2 iter2: key2
Re: Find in assoc array then iterate
Hi Sergey, While the unordered map doesn't guarantee an ordering (since its contents are hashed), the order should remain static if you don't insert or delete. Hi JG, Thanks for the red-black tree reference. I'll read up on it in case I need it but I'd prefer to use the built-in O(1) hash map. Hi again Ali!, I did consider a sort, but it has a number of downsides: - it's O(n ln(n)) - if I'm understanding correctly, it would require additional memory - Not relevant to my problem but, if you had duplicates, it wouldn't handle stopping in the middle of some duplicates and re-starting; only iterators support this. One could more easily copy the keys into an array, and iterate on them. Thus, I was hoping to find a member function that would support this. Steven, Just because you don't see the value doesn't mean I don't. You should try to be more helpful, or don't bother.
Find in assoc array then iterate
I'm trying to do this equivalent C++: unordered_map map; for (auto i = map.find(something); i != map.end(); ++i) ...do something with i... in D, but obviously with an associative array. It seems that it's quite easy to iterate through the whole "map", but not start from the middle. Am I missing something obvious (or not so obvious) ?
Re: Why are structs and classes so different?
Hi again Ali! On Monday, 16 May 2022 at 21:58:09 UTC, Ali Çehreli wrote: I for one misunderstood you. I really thought you were arguing that struct and class should be the same. To be specific: - My *primary* concern is that: Foo foo; is undefined behavior waiting to happen, that I can't detect at a glance. - A *secondary* goal would be for class objects to be able to have deterministic destructors like structs. The first looks trivial: Just have it allocate a Foo by default. The second looks like it could have been designed that way, albeit with other minor changes to the language, and, I was curious why it wasn't. C++ is proof that it can indeed work the other way. However, for it to work correctly, programmers must follow guidelines. Here are four: But again, from this discussion, it seems D could have simply had pass-by-reference for class objects, preserving everything D strives for, but by-value stack initialization, providing what people expect from stack objects. There's no need to drag C++ design into it. C++ does not use terms "value type" and "reference type" to make any distinction but the rules above are proof enough for me that C++ implicitly divides user-defined types into such categories. I would beg to differ here. In C++, all types are value types, until you add punctuation. This is one of the things that I like about C++, that I trip over in other languages. e.g. In Rust, there are 2 similar types, is it int and float, where one is copyable and the other move-only? How can you write generic code in that environment? Yes, in C++, you have to worry about slicing and copying singletons, but these are problems in front of you. It's the problems that sneak up behind you that I worry about. Ok, I think I see better now. You would like members of a class recursively placed next to each other in memory. What if a polymorphic type had a polymorphic member? You mean like a string? I don't have a problem with this: class MyString { uint length; ...pointer to data... } void func() { MyString s; if (s.length == 0) // I want this to be perfectly safe. writeln("empty"); // 's' destroyed here, could do something useful } In D, some objects can do this; some can't! I don't understand that example. I see a programmer error of casting a Foo to a Bar. Correct, I was responding to a comment. I was pointing out that the only "slicing" that we need to worry about with by-reference is if we are already doing something wrong, and that D won't help you there. Did you mean C#? C# is like D: structs are value types and classes are reference types. You missed the part where I said, "ignoring structs". :-) With all due respect, based on other conversation here, may I assume you those projects were based on C++? If you also had any language with reference types like Python, did you have the similar issues with those languages? Python is a toy language, right? I'm not aware of any large projects in it. (The largest I worked with was 138 files, 31k lines - tiny.) Java would be a better comparison, but it has auto-closable objects, unlike D and Python. Perhaps it has succeeded because there are so few types that *aren't* by reference. Perhaps one just gets used to it. (I've written Android apps, but I would never write a long-running service in it.) This is unfortunate for D where you have to keep track. I accepted D's struct/class separation since the beginning and never had any issue with it. It just worked for me. Perhaps you are familiar with the types that you work with on a daily basis. Perhaps the project is small. Perhaps your IDE colors classes brightly. I don't know, but an anecdote doesn't mean much compared to the fact that nearly all large projects are in C++, and I don't mean "due to inertia." I mean, "and they're successful."
Re: Why are structs and classes so different?
On Monday, 16 May 2022 at 19:06:01 UTC, Alain De Vos wrote: A new syntax like "*" should introduce something new. If it's not needed for classes why introduce it. Hi Alain! I have to sympathize with Johan. If you see: Foo foo = get_foo(); call_function(foo); can 'foo' change in 'call_function()' ? Is it by-reference or is it by value? Foo* foo = get_foo(); How about now? Pretty obvious. call_function(); Also obvious. To re-phrase your claim, a new syntax doesn't need to introduce something new. Syntax is there to convey information. If you don't know if something is a class name it class_blabla. Just remember the effect of "=" ah, I see the problem. You've never worked in a large code-base written by thousands of other people. I do every day. I can't make them name things in any special way. But when I see the above code, I need to know exactly what it does with just a scan.
Re: Why are structs and classes so different?
Great responses, everyone. I'll try to address all of them. Mike, I know the rules. I was asking, "Why is it this way? Why was it designed this way? What bad thing happens the other way?" When I think about most things in D, I can at least think of a reason, even if I don't agree with it. But not putting class objects on the stack makes no sense to me (assuming we still pass by reference.) Reasons below. Ola, your response is very interesting. I would say that assignment isn't any more or less of an issue, as long as you pass by reference: // Using C syntax to make intent clear. class Foo { int x; } class Bar: Foo { int y; } void func1(Bar* bar) { bar.y = 4; // boom } void func2(Bar* bar) { Foo* foo = bar; foo.x = 3; // this is fine foo = new Foo; func1(cast(Bar*)(foo)); // uh oh } Illuminating comment about the ABI. Ali, I've never liked the distinction between 'struct' and 'class' in C++ either, but that's no reason to actually make them different. It's a reason to remove 'class' and save the keyword. re: pass-by-reference and performance, agreed, this is why we pass integers in registers. But a struct on the stack is just as fast to access locally through a pointer register - "[bp+6]" - as remotely, yes? Finally, 'scope' is nice but it doesn't solve the segfault issue. HS Teoh: See above for my responses about assignment and 'scope'. bauss: "But that's not entirely true as you can allocate a struct on the heap as well." forkit: "where they live in memory should be less of the programmers concern, and more an implementation issue (although some programmers will of course consider this as well)." Precisely. You can allocate structs, and you can put class objects in 'scope' variables. (I'm not sure if this was your intent, forkit, but) a class object can live on the stack just as easily as on the heap, as long as you pass by reference. The only danger is if a called function tries to own a stack allocated object, but this is a concern for heap allocated objects too. This is why C++ has moveable types and unique_ptr. Walter, Thanks for the insightful reply! I'm getting the sense that the decision was made in order to make the language simpler. That is, ignoring struct's, D went the Java path: You can have any color you like, as long as it's grey. I agree that C++'s organic growth allows programmers to do things they shouldn't, and requires that they be more careful. But I would not have gone from there to Java. I think an interesting middle-ground would be a language that had "template" types - Copyable, MoveOnly, Interface, Singleton, FactoryBuilt, etc. I've learned from all the projects that I've been on that we need all these types. We can either ask programmers to hand-craft them, or we can provide them. Note that in C++, we can get pretty close: class Foo: Moveable { ... And then there's the segfault issue. I think that, if we're going to ignore a huge problem like that, there has to be very strong reasons. From this discussion, it doesn't sound like they were very strong. Of course, it's done and there's little changing it. A seemingly harmless fix would be to not require 'new': Foo foo; // this allocates an object Foo foo = null; // this does not Foo foo = function(); // this doesn't either In fact, I suspect we could make that change today and few would notice. Nevertheless, I'm still a little shocked that this isn't existing behavior. cheers all
Re: Why are structs and classes so different?
Hi Mike (and Guillaume, since you posted the same link), Thanks for the long explanation. I've been programming in C++ full time for 32 years, so I'm familiar with slicing. It doesn't look to me like there's a concern here. There seem to be a couple different questions here. I suspect that you answered a different one than I asked. One question is, how should we pass objects - by value or by reference? In C++, you can do either, of course, but you take your chances if you pass by value - both in safety AND PERFORMANCE. The bottom line is that no one passes by value, even for PODs (although we may return even large objects.) But I asked a different question: Why can't I put a class object on the stack? What's the danger? Note that operating on that object hasn't changed. If I pass by reference, it's no different than if I had created a reference. One might say, Well, if D creates by value, then it has to pass by value. But it doesn't; it has the 'ref' keyword. One might want to avoid passing by value accidentally. Ok, one could have D pass class objects by reference implicitly. I don't like things silently changing like that, so one might have D forbid all but pass by 'ref' or pointer for class objects. In any case, this doesn't quite address the instantiate by value issue. If there's still a case where putting an object on the stack breaks, I would greatly appreciate seeing a few lines of example code. I hope Ali's answer isn't the real reason. I would be sad if D risked seg faults just to make class behavior "consistent". thx
Why are structs and classes so different?
I've done some scripting in D over the years but I never dug into D until recently. I'm going through Learning D and I was reminded that structs and classes are so different. - struct methods are non-virtual while class methods are virtual - Thus, structs can't inherit, because how would you find the child's destructor given a parent pointer? - On the stack, structs by-value but classes are by-reference I'm trying to understand why it is this way. I assume that there's some benefit for designing it this way. I'm hoping that it's not simply accidental, historical or easier for the compiler writer. One problem that this causes is that I have to remember different rules when using them. This creates the additional load of learning and remembering which types are which from someone else's library. A bigger problem is that, if I have a struct that I suddenly want to inherit from, I have to change all my code. In addition to that work, in both of these cases, one could easily do it wrong: // Fine with a struct, fatal with a class. Foo foo; At least in C++, the compiler would complain. In D, not even a warning. Why is it this way? What is the harm of putting a class object on the stack?
Template arg deduction
I'm trying to use some fairly simple template argument deduction, but maybe I'm not getting the syntax correct. C++ doesn't event blink at something like this, but D is giving me: temptest.d(18): Error: template temptest.func cannot deduce function from argument types !()(bar), candidates are: temptest.d(10):func(T)(foo!T.bar f) I guess D can't crack open a type like that? ``` template foo(T) { class bar { T t; } } void func(T)(// 10 foo!(T).bar f) { } int main() { foo!int.bar fi; func(fi);// 18 } ```
Re: Trying to search a range
On Saturday, 3 July 2021 at 01:15:20 UTC, Paul Backus wrote: The error message here is actually telling you exactly what the problem is: `findSplit` requires a forward range, but the range returned by `File.byLine` is not a forward range, just an input range. Hey Paul, Thanks for the pointer. I doubt I would have figured out that surprising detail. Nevertheless, now I know where to look for the exact error. :-)
Trying to search a range
Sorry if this is too simple of a question, but I'm trying to simply find a blank line in a file. I'm doing something like: ``` auto file = File("somefile"); auto r = file.byLine(); auto tup = r.findSplit(""); // tup[0] would contain the range before the line, // tup[2] would contain the range after, not unlike findSplit ``` Unfortunately, ldc2 is saying: ``` split.d(8): Error: template std.algorithm.searching.findSplit cannot deduce function from argument types !()(ByLineImpl!(char, char), string), candidates are: /usr/lib/ldc/x86_64-linux-gnu/include/d/std/algorithm/searching.d(2882): findSplit(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) with pred = "a == b", R1 = ByLineImpl!(char, char), R2 = string must satisfy the following constraint: isForwardRange!R1 ``` I can't tell if findSplit() only works on strings, or if maybe I'm trying to compare different types of strings, or if I'm overtaxing the type deduction system.
q about slices, strings
I saw on the dlang.org homepage a sample of code that looks a little strange to me: int[char[2]] aa; auto arr = "ABBBA"; foreach (i; 0 .. arr.length - 1) aa[arr[i .. $][0 .. 2]]++; which I changed to: aa[arr[i .. i+2][0 .. 2]]++; in the hopes of only doing: aa[arr[i .. i+2]]++; The question is: Why is the [0..2] still necessary? Without it, dmd (on that homepage) complains: cannot implicitly convert expression arr[i..i + 2LU] of type string to char[2] The spec says that: 10.22 Slices ... 6. If the slice bounds can be known at compile time, the slice expression is implicitly convertible to an lvalue of static array. 7. The following forms of slice expression can be convertible to a static array type: e An expression that contains no side effects. a, b Integers (that may be constant-folded). Form The length calculated at compile time ... arr[e .. e+b] b So why isn't arr[i .. i+2] convertible? Is dmd behind the spec here?