C++ / Why Iterators Got It All Wrong

Ilya Yaroshenko via Digitalmars-d digitalmars-d at puremagic.com
Sun Sep 3 07:19:19 PDT 2017


On Sunday, 3 September 2017 at 12:35:16 UTC, Moritz Maxeiner 
wrote:
> On Sunday, 3 September 2017 at 09:24:03 UTC, Ilya Yaroshenko 
> wrote:
>> On Sunday, 3 September 2017 at 02:43:51 UTC, Moritz Maxeiner 
>> wrote:
>>> On Sunday, 3 September 2017 at 02:08:20 UTC, Ilya Yaroshenko 
>>> wrote:
>>>> On Tuesday, 29 August 2017 at 12:50:08 UTC, Robert M. M√ľnch 
>>>> wrote:
>>>>> Maybe of interest: 
>>>>> https://www.think-cell.com/en/career/talks/iterators/#1
>>>>>
>>>>> I haven't read everything, so not sure if it worth to take 
>>>>> a look.
>>>>
>>>> Iterators has no proper alternative when one need to 
>>>> implement generic tensor library like Mir Algorithm [1].
>>>
>>> Out of curiosity: Could you elaborate on what the issues are 
>>> with using a range based API internally (if it's performance, 
>>> the "why")?
>>
>> There are three general kinds of dynamic dense tensors. Mir 
>> implements all of them:
>>
>> 1. Contiguous tensors. Their data is located contiguously in 
>> memory. Single dense memory chunk. All strides between 
>> subs-tensors can be computed from lengths.
>>
>> 2. Canonical tensors. Only data for one dimension is dense, 
>> other dimensions has strides that can not be computed from 
>> lengths. BLAS matrixes are canonical tensors: they have two 
>> lengths and one stride.
>>
>> 3. Universal tensors. Each dimension has a stride. Numpy 
>> ndarrays are universal tensors.
>>
>> Finite random access range Issues:
>>
>> 1. Main API issue is that full featured random access range 
>> (RAR) API (with slicing, and all opIndex* operators like 
>> `[]*=` ) is much larger comparing with unbounded random access 
>> iterator (the same API as pointer).
>
> Won't you have to implement opIndex* operators either way in 
> order to use the `a[]` syntax? The main difference I can see 
> should be that you'd also have to implement the InputRange 
> (front,popFront,empty), ForwardRange (save), and 
> BidirectionalRange (back,popBack) members, which if you don't 
> need them, is indeed understandably too much?

front,
popFront,
empty,
save,
back,
popBack,
opIndexOpAssign,
2 x opIndex(.opSlice) for rhs scalars and ranges
2 x opIndexAssign(.opSlice) for rhs scalars and ranges
2 x opIndexOpAssign(.opSlice) for rhs scalars and ranges

Plus, because of range topology slicing may be slower good to add 
this ones:

popFrontN
popFrontExactly,
popBackN
popBackExactly,

and maybe i forgot something ...

>>
>> 2. Random access range holds its length. Yes, Phobos has 
>> infinity ranges (mir hasn't), but the practice is that almost 
>> any RAR constructed using Phobos has length and you can not 
>> strip when type is constructed. Anyway, infinity RAR is just a 
>> pointer/iterator that can move forward but can not move 
>> backward. Tensors hold their own lengths for each dimensions, 
>> so range's length is just a useless payload.
>
> I'm not sure what you mean here. Is this still about accessing 
> the elements of one tensor? If so, what do Phobos' ranges have 
> to do with your tensor type's API?

ndslice is:
1. Slice structure with a huge amount of features from mir 
algorithm
2. multidimensional RAR: .front!0, front!1, .front!(N - 1) and 
all other generalized RAR primitves including multidimensional 
index [1, 2, 3, ...] and slicing [1 .. 4, 3 .. $].
3. Common one dimensional RAR on top of outer most dimension. So 
one can use Phobos funcitons for Slice structure. For example, a 
matrix will be iterated row-by-row.

But this paragraph was about another issue:

struct BLASMatrixTypeBasedOnRanges
{
    double[] _data; // arrays is the simplest RAR
    size_t rows, cols;
    size_t stride;
}

sizeof(YourBLASMatrixTypeBasedOnRanges) == size_t.sizeof * 5;

in other hand:

sizeof(Slice!(Canonical, [2], double*)) == size_t.sizeof * 4;

Ranges requires for 25% more space for canonical matrixes then 
iterators.

>>
>> 3. Ranges can not move backward (auto b = a[-4 .. $].). This 
>> means one can not implement dynamic `reversed`(flip) [1, 2] 
>> operation for dimensions that have strides (universal or 
>> canonical tensors). _Dynamic_ means that instead of construct 
>> a new type with reversed order for a dimension, we just negate 
>> the corresponding stride and move the cursor.
>
> Right, that is indeed a limitation of the range abstraction 
> (though a type could expose both functionality to move 
> backwards _and_ the range API) and if you need that, it makes 
> sense not to use ranges.
>
> Thanks for the summary of issues :)




More information about the Digitalmars-d mailing list