Jump to page: 1 2 3
Thread overview
Weird issue with std.range.iota.length
Feb 12, 2016
Meta
Feb 12, 2016
Rikki Cattermole
Feb 12, 2016
Meta
Feb 12, 2016
Rikki Cattermole
Feb 12, 2016
Meta
Feb 12, 2016
Jonathan M Davis
Feb 12, 2016
tsbockman
Feb 12, 2016
tsbockman
Feb 12, 2016
Jonathan M Davis
Feb 12, 2016
Meta
Feb 12, 2016
Jonathan M Davis
Feb 12, 2016
ixid
Feb 12, 2016
Jonathan M Davis
Feb 12, 2016
H. S. Teoh
Feb 12, 2016
H. S. Teoh
Feb 12, 2016
Meta
Feb 12, 2016
Meta
Feb 14, 2016
w0rp
Feb 14, 2016
Jonathan M Davis
Feb 21, 2016
Meta
February 12, 2016
If you try to compile this code, it will currently not work:

foreach (n; iota(1UL, 1000).parallel)
{
    //...
}


This is because of how the length is calculated by iota:

auto iota(B, E)(B begin, E end)
if (isIntegral!(CommonType!(B, E)) || isPointer!(CommonType!(B, E)))
{
    import std.conv : unsigned;

    //...

    //The return type of length. When either begin or end
    //is ulong, then IndexType == ulong
    alias IndexType = typeof(unsigned(end - begin));

    static struct Result
    {
        private Value current, pastLast;

        //...

        @property IndexType length() const
        {
            return unsigned(pastLast - current);
        }
    }

    return Result(begin, end);
}


And because std.parallelism.TaskPool.defaultWorkUnitSize takes a size_t, which with a 32-bit DMD is uint.

What I want to know is, is this considered a bug? If so I will submit a pull request to fix it.
February 12, 2016
On 12/02/16 6:51 PM, Meta wrote:
> If you try to compile this code, it will currently not work:
>
> foreach (n; iota(1UL, 1000).parallel)
> {
>      //...
> }
>
>
> This is because of how the length is calculated by iota:
>
> auto iota(B, E)(B begin, E end)
> if (isIntegral!(CommonType!(B, E)) || isPointer!(CommonType!(B, E)))
> {
>      import std.conv : unsigned;
>
>      //...
>
>      //The return type of length. When either begin or end
>      //is ulong, then IndexType == ulong
>      alias IndexType = typeof(unsigned(end - begin));
>
>      static struct Result
>      {
>          private Value current, pastLast;
>
>          //...
>
>          @property IndexType length() const
>          {
>              return unsigned(pastLast - current);
>          }
>      }
>
>      return Result(begin, end);
> }
>
>
> And because std.parallelism.TaskPool.defaultWorkUnitSize takes a size_t,
> which with a 32-bit DMD is uint.
>
> What I want to know is, is this considered a bug? If so I will submit a
> pull request to fix it.

So I assume if you compiled as 64bit this will work then?
February 12, 2016
On Friday, 12 February 2016 at 05:59:32 UTC, Rikki Cattermole wrote:
> So I assume if you compiled as 64bit this will work then?

Yes, because `unsigned(begin - end)` will yield a ulong regardless, but size_t will then be a ulong as well.
February 12, 2016
On 12/02/16 7:19 PM, Meta wrote:
> On Friday, 12 February 2016 at 05:59:32 UTC, Rikki Cattermole wrote:
>> So I assume if you compiled as 64bit this will work then?
>
> Yes, because `unsigned(begin - end)` will yield a ulong regardless, but
> size_t will then be a ulong as well.

So an edge case.
February 12, 2016
On Friday, 12 February 2016 at 06:21:45 UTC, Rikki Cattermole wrote:
> On 12/02/16 7:19 PM, Meta wrote:
>> On Friday, 12 February 2016 at 05:59:32 UTC, Rikki Cattermole wrote:
>>> So I assume if you compiled as 64bit this will work then?
>>
>> Yes, because `unsigned(begin - end)` will yield a ulong regardless, but
>> size_t will then be a ulong as well.
>
> So an edge case.

Yes, it's an edge case, although it's a pretty easy one to run into.
February 12, 2016
On Friday, 12 February 2016 at 05:51:34 UTC, Meta wrote:
> If you try to compile this code, it will currently not work:
>
> foreach (n; iota(1UL, 1000).parallel)
> {
>     //...
> }
>
>
> This is because of how the length is calculated by iota:
>
> auto iota(B, E)(B begin, E end)
> if (isIntegral!(CommonType!(B, E)) || isPointer!(CommonType!(B, E)))
> {
>     import std.conv : unsigned;
>
>     //...
>
>     //The return type of length. When either begin or end
>     //is ulong, then IndexType == ulong
>     alias IndexType = typeof(unsigned(end - begin));
>
>     static struct Result
>     {
>         private Value current, pastLast;
>
>         //...
>
>         @property IndexType length() const
>         {
>             return unsigned(pastLast - current);
>         }
>     }
>
>     return Result(begin, end);
> }
>
>
> And because std.parallelism.TaskPool.defaultWorkUnitSize takes a size_t, which with a 32-bit DMD is uint.
>
> What I want to know is, is this considered a bug? If so I will submit a pull request to fix it.

What it comes down to is that length should always be size_t. That's what it is for arrays, and that's what most code expects. Allowing other types just causes trouble for generic code. However, in the case of iota with long, if length is size_t, then on 32-bit systems, it's possible to have a range from iota which is longer than size_t can represent (much as it would normally be crazy to have a range that long). So, at some point, someone made it so that iota uses ulong for length instead of size_t when it's a range of longs or ulongs. It's the only thing in Phobos that does, and it causes problems. Changing it back to size_t has been discussed but not agreed upon. But we're between a rock and a hard place with this one. There is no clean solution.

Personally, I'd very much like to see iota just always use size_t for length like every other range (the only ranges which would be affected would be ludicrously long anyway, and it would only affect 32-bit programs). But that hasn't happened yet, so iota over longs and ulongs doesn't behave nicely on 32-bit systems.

Regardless of which way we go, the problem will _eventually_ go away when 32-bit systems finally die out, but that's likely to take a while.

- Jonathan M Davis
February 12, 2016
On Friday, 12 February 2016 at 08:11:57 UTC, Jonathan M Davis wrote:
> Regardless of which way we go, the problem will _eventually_ go away when 32-bit systems finally die out, but that's likely to take a while.

Many micro-controllers will probably remain 32-bit indefinitely; 64-bit is just a waste of power for most applications. Even if you want to ignore non-PC/smartphone CPUs, we'll still have the same problem whenever someone gets around to implementing 128-bit cent/ucent.

Ultimately, the only real solution to this kind of problem is some combination of:

1) Select the right type for the job (don't use `ulong` when you really mean `size_t`), and

2) Bounds checking. If iota's length is not expressible as a `size_t`, convert it to one *safely* using `std.conv.to()`.

Anything less is just asking for bugs.
February 12, 2016
On Friday, 12 February 2016 at 10:34:32 UTC, tsbockman wrote:
> Bounds checking. If iota's length is not expressible as a `size_t`, convert it to one *safely* using `std.conv.to()`.

Just to clarify - the bit about `std.conv.to()` is just an example.

A better solution in this specific case would be to check once, while the iota() range is being constructed, whether its length is expressible as a `size_t` or not.
February 12, 2016
On Friday, 12 February 2016 at 10:37:31 UTC, tsbockman wrote:
> On Friday, 12 February 2016 at 10:34:32 UTC, tsbockman wrote:
>> Bounds checking. If iota's length is not expressible as a `size_t`, convert it to one *safely* using `std.conv.to()`.
>
> Just to clarify - the bit about `std.conv.to()` is just an example.
>
> A better solution in this specific case would be to check once, while the iota() range is being constructed, whether its length is expressible as a `size_t` or not.

Yeah. I think that that's the best approach, but it does unfortunately have the downside that you pretty much can't have a range over all of the longs or ulongs on 32-bit systems (though fortunately that should rarely be an issue, and I don't really see a fix for that that doesn't cause problems with length and other ranges).

- Jonathan M Davis
February 12, 2016
On Friday, 12 February 2016 at 08:11:57 UTC, Jonathan M Davis wrote:
> What it comes down to is that length should always be size_t. That's what it is for arrays, and that's what most code expects. Allowing other types just causes trouble for generic code. However, in the case of iota with long, if length is size_t, then on 32-bit systems, it's possible to have a range from iota which is longer than size_t can represent (much as it would normally be crazy to have a range that long). So, at some point, someone made it so that iota uses ulong for length instead of size_t when it's a range of longs or ulongs. It's the only thing in Phobos that does, and it causes problems. Changing it back to size_t has been discussed but not agreed upon. But we're between a rock and a hard place with this one. There is no clean solution.
>
> Personally, I'd very much like to see iota just always use size_t for length like every other range (the only ranges which would be affected would be ludicrously long anyway, and it would only affect 32-bit programs). But that hasn't happened yet, so iota over longs and ulongs doesn't behave nicely on 32-bit systems.
>
> Regardless of which way we go, the problem will _eventually_ go away when 32-bit systems finally die out, but that's likely to take a while.
>
> - Jonathan M Davis

What about adding another overload of iota, say, iotaEx or something along those lines. All it would do is the following:

auto iotaEx(B, E)(B begin, E end)
{
    assert(unsigned(end - begin) <= size_t.max);

    static struct Result
    {
        typeof(iota(begin, end)) payload;

        @property size_t length()
        {
            return cast(size_t)payload.length;
        }

        alias payload this;
    }

    return Result(iota(begin, end));
}
« First   ‹ Prev
1 2 3