Array types not treated uniformly when passed as ranges

Jonathan M Davis jmdavisProg at gmx.com
Mon Feb 14 19:59:52 PST 2011


On Monday 14 February 2011 18:18:39 jam wrote:
> Hi all,
> 
> Just curious as to the difference in the built-in variable length
> array vs. the std.container.Array and fixed length arrays when it
> comes to using them in functions that take Ranges.
> 
> For instance the following does not compile:
> 
> import std.algorithm;
> import std.stdio;
> import std.range;
> import std.conv;
> import std.container;
> import std.array;
> 
> void main() {
> 
>     int[5] builtin_fixed;
>     int[] builtin_variable;
>     Array!(int) con_array;
> 
>     con_array.length(5);
>     builtin_variable.length = 5;
> 
>     fill(builtin_variable, 9); //ok, no error
>     isSorted(builtin_variable); //ditto
> 
>     //The following 4 statements produce errors
>     fill(builtin_fixed, 9);
>     fill(con_array, 9);
> 
>     isSorted(con_array);
>     isSorted(builtin_fixed);
> 
> }
> 
> The errors are variations on:
> 
> Error: template std.algorithm.fill(Range,Value) if
> (isForwardRange!(Range) && is(typeof(range.front = filler))) does not
> match any function template declaration
> Error: template std.algorithm.fill(Range,Value) if
> (isForwardRange!(Range) && is(typeof(range.front = filler))) cannot
> deduce template function from argument types !()(int[5LU],int)
> 
> If I change those 4 statements to:
> 
>     fill(builtin_fixed[], 9);
>     fill(con_array[], 9);
> 
>     isSorted(con_array[]);
>     isSorted(builtin_fixed[]);
> 
> effectively passing ranges (std.container.Array!(int).Array.Range in
> the case of con_array, and int[] for builtin_fixed) which then works
> as expected.  This all makes sense, and it's easy enough to write
> wrappers,   but I would (well and I did) expect the first way to just
> work.   This may just be a nitpick I guess, but being new to the
> language this little detour involved quite a bit of time research (not
> a bad thing, I did learn quite a bit in the process), but makes me
> wonder if I am missing something fundamental regarding when I should
> be using these different array types.

It's because an Array is not a range. Dynamic arrays are a bit special in that 
they're both a container and a range. An Array is just a container. But 
honestly, you wouldn't really want it to work.

When you pass a dynamic array, it creates a new array which references the old 
one. That means that as long as you don't increase the size of either array or 
assign another array to one of them, they will continue to point to the same 
data and altering one's elements will alter the others, since their elements are 
the same. However, increasing the size of either array might cause a 
reallocation (it might not if there's room passed the end of the array) and they 
would then point to two separate sets of data. This means that you can pass an 
array to a range-based function and it can continually use popFront or popBack 
to take a smaller and smaller slice of the array. The elements can be altered if 
that's what the algorithm does, but altering the size of the passed in array 
(which popFront and popBack would obviously do) does _not_ alter the size of the 
original array. So, it's safe for the range-based function to treat the passed 
in array as a range without altering the original array.

However, this won't work for containers in general. If you pass an Array, you're 
passing the container. The passed in Array is identical to the original 
(currently, Array is a struct with reference semantics thanks to internal 
reference counting, but soon it will become a class and more obviously have 
reference semantics). As such, if you alter the passed in Array, you alter the 
original. So, if you were to continuously pop the front off of the passed in 
Array, you would be continually popping the front off of the original Array. So, 
a function like find would actually remove the front part of your Array as it 
processed it, and if what you were trying to find wasn't there, you'd have an 
empty Array. The way to fix this is to not have Array be a range. Instead, you 
have a helper type which is a range over it. You get that with the slice 
operator.

You don't have the problem with arrays that you'd have with user-defined 
container types, because the semantics of arrays are a bit odd when you copy 
them. So, if anything were faulty, it would be the built in arrays, not user-
defined container types like Array. It is quite handy to have arrays work how 
they work, however, so that's not likely to change.

So, if you want consistency, use [] even when using arrays. It's the same thing 
really. The resulting code probably isn't even any different, and you have to do 
that with static arrays anyway. So, it's just plain more generic to always slice 
when passing to a range function.

- Jonathan M Davis


More information about the Digitalmars-d-learn mailing list