Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

d2: assigning ranges/iterators to array slices

Tags:

iterator

range

d

Consider following code:

enum size = 16;
double[size] arr1 = [...];
double[size] arr2 = [...];
process = (double x) { return (x + 1); };

arr2[] = map!(process)(arr1[]); // here

I have trouble converting results of map back to my plain array. Problem applies not only to map, but also to take, repeat and all those fine tools from std.algorithm and std.range that operate on ranges.

On this assignment, I get Error: cannot implicitly convert expression (map(arr1[])) of type Result to double[]. How can I evaluate range to array without using

uint i = 0;
foreach (x; map!(process)(arr1[])) {
    arr2[i] = x;
    i++;
}

?

Additionally, can someone please explain, why I must call map!(process)(arr1[]) instead of map!(process)(arr1) with static arrays? Shouldn't static arrays be compatible with dynamic for means of iteration, or I don't get something?

Also, it seems that straightforward enumeration syntax foreach (index, item; sequence) does not work for ranges - are there workarounds? I guess the reason is the same as why ranges cannot be assigned to array slices.

like image 370
toriningen Avatar asked Mar 29 '12 22:03

toriningen


1 Answers

Functions such as map and filter return ranges, not arrays, so simply assigning to an array isn't going to work any more than assigning a string to wstring is going to work. They're different types. And for many range-based functions (including map and filter), the ranges they return are actually lazy in order to avoid unnecessary computation, which makes them that much less compatible with an array. The solution is to use std.array.array, which takes a range and creates a dynamic array from it. So, you could do

auto arr = array(map!process(origArray));

However, I would advise not converting a range into an array before you actually need to, since it can result in unnecessary computations, and it means allocating a new array. If you actually need an array, then by all means, use std.array.array to convert the range, but operating on the range can often be more efficient if you don't need an actual array. However, if you want to convert the result to a static array as opposed to a dynamic one, you're probably better off just assigning each element in a loop (and maybe skipping map altogether), since using std.array.array will then have allocated a dynamic array that you won't be using once you've assigned to the static array. It's a waste of memory.

Also, be aware that using static arrays with range-based functions can be risky in that they must slice the static array to get a dynamic array for the range-based functions to process, and if that dynamic array escapes the scope that the static array was declared in, then you're leaking references to data which no longer exists. For example,

auto func()
{
    int[5] arr;
    return map!process(arr[]);
}

would be very bad. However, as long as you're done using the slice and nothing refers to it anymore (including any ranges that might have been created) before you exit the scope with the static array in it, you should be fine. It is something to be careful of though.

As for the question about having to slice static arrays, you really should ask that as a separate question, but two existing questions that relate to it are this one and this one. What it pretty much comes down to is that IFTI (Implicit Function Template Instantiation) instantiates using the exact type that it's given, and a static array is neither a dynamic array nor a range, so any templated function which specifically requires a dynamic array or a range will fail to compile with a static array. The compiler will implicitly slice static arrays to convert them to dynamic arrays for functions which explicitly take dynamic arrays, but those sort of implicit conversions don't happen with template instantiation, so you must explicitly slice static arrays to pass them to range-based functions.

As for the question about using foreach with indices and ranges, again, you shouldn't be asking multiple questions in the same question. Please post separate questions for each question that you have. What it comes down to though is that

foreach(elem; range)
{
    //stuff
}

gets lowered to something close to

for(; !range.empty; range.popFront())
{
    auto elem = range.front;
    //stuff
}

And that doesn't involve indices at all. It could be change to create an index variable for you, but it doesn't always make sense for ranges to have their index iterating by one like that on every iteration (much as it usually would be fine), and so that hasn't been done. It's simple enough to add your own counter though.

{
    size_t i;
    foreach(elem; range)
    {
        //stuff
        ++i;
    }
}

opApply does support using indices with foreach, but it isn't a range, and doesn't work with range-based functions.

like image 65
Jonathan M Davis Avatar answered Oct 23 '22 16:10

Jonathan M Davis