Thread overview
Rewriting a c++ template to D (replacing iterator with ranges "properly")
Jan 26, 2018
aliak
Jan 26, 2018
aliak
Jan 26, 2018
Meta
Jan 26, 2018
aliak
Jan 26, 2018
Simen Kjærås
Jan 27, 2018
aliak
Jan 26, 2018
Simen Kjærås
Jan 26, 2018
aliak
January 26, 2018
It basically steps through in a stride and sets the checkpoints to false.

C++:
template <typename /*RandomAccessIterator*/ It, int /*Integer*/ N>
void mark(It begin, It end, N step) {
  assert(begin != end)
  *begin = false;
  while (end - begin > step) {
    begin = begin + step;
    *begin = false;
  }
}

For D this is what I figured would be the way?

void mark(R, N)(auto ref R range, N step)
if (
  /* 1 */ isIntegral!N
  /* 2 */ && isRandomAccessRange!R
  /* 3 */ && is(ElementType!R == bool)
  /* 4 */ && hasAssignableElements!R
) {
  range.front = false;
  while (!range.empty) {
    range.popFrontN(N);
    range.front = false;
  }
}

I have a more specific question too:

1) I've seen some phobos code checking for assignability like this:

  is(typeof(range.front = false))

... is that an advantage of that over hasAssignableElements? Or is that just basically combining constraints 3 and 4 which I have above?

2) Say I wanted to restrict to only lvalue ranges passed in as inputs. Does that mean I use hasLvalueElements as a constraint or is remove the "auto" and just have a ref parameter sufficient?

Cheers
January 26, 2018
On Friday, 26 January 2018 at 14:16:04 UTC, aliak wrote:
>   range.front = false;
>   while (!range.empty) {
>     range.popFrontN(N);
>     range.front = false;
>   }
> }

Oops, this should be:

while (!range.empty) {
  range.front = false;
  range.popFrontN(N);
}

January 26, 2018
On Friday, 26 January 2018 at 14:16:04 UTC, aliak wrote:
> 1) I've seen some phobos code checking for assignability like this:
>
>   is(typeof(range.front = false))
>
> ... is that an advantage of that over hasAssignableElements? Or is that just basically combining constraints 3 and 4 which I have above?

Where did you see this? That's got to be some very old code; I can't think of any instance where you would not want to use `hasAssignableElements` instead.
January 26, 2018
On Friday, 26 January 2018 at 14:16:04 UTC, aliak wrote:
> It basically steps through in a stride and sets the checkpoints to false.
>
> C++:
> template <typename /*RandomAccessIterator*/ It, int /*Integer*/ N>
> void mark(It begin, It end, N step) {
>   assert(begin != end)
>   *begin = false;
>   while (end - begin > step) {
>     begin = begin + step;
>     *begin = false;
>   }
> }

what is N here? You're declaring it to be an int value in the template<> definition, and then use it as a type in the function definition.



> For D this is what I figured would be the way?
>
> void mark(R, N)(auto ref R range, N step)
> if (
>   /* 1 */ isIntegral!N
>   /* 2 */ && isRandomAccessRange!R
>   /* 3 */ && is(ElementType!R == bool)
>   /* 4 */ && hasAssignableElements!R
> ) {
>   range.front = false;
>   while (!range.empty) {
>     range.popFrontN(N);
>     range.front = false;
>   }
> }

Not exactly. range.front will assert after the last popFrontN (since the range is empty).

You'll need something like this:

void main(R, N)(R range, N step)
if (isIntegral!N &&
    isRandomAccessRange!R &&
    is(ElementType!R == bool) &&
    hasAssignableElements!R)
{
    if (range.empty) return;
    do
    {
        range.front = false;
        range.popFrontN(N);
    } while (!range.empty);
}


> 1) I've seen some phobos code checking for assignability like this:
>
>   is(typeof(range.front = false))
>
> ... is that an advantage of that over hasAssignableElements? Or is that just basically combining constraints 3 and 4 which I have above?

It's trying to combine 3 and 4 I think, but it fails because this is allowed in D:

int a;
a = false;


> 2) Say I wanted to restrict to only lvalue ranges passed in as inputs. Does that mean I use hasLvalueElements as a constraint or is remove the "auto" and just have a ref parameter sufficient?

You'll want to pass the range as ref. hasLvalueElements checks if the elements have lvalue semantics, not if the range itself does. Here's a range that has lvalue elements, for which passing it as ref or non-ref makes a huge difference:

import std.range;

struct R {
    int[3] elements;
    int index;

    ref auto front() {
        return elements[index];
    }

    void popFront() {
        ++index;
    }

    bool empty() {
        return index >= elements.length;
    }
}

unittest {
    assert(hasLvalueElements!(R));
    auto a = R([1,2,3], 0);
    auto b = a;
    b.front = 4;
    assert(a.elements == [1,2,3]);
    assert(b.elements == [4,2,3]);
}

As we can see, b is a complete copy of a, and changing b does not change a. The exact same behavior would occur if a was passed by value to a function.

--
  Simen
January 26, 2018
On Friday, 26 January 2018 at 14:59:09 UTC, Simen Kjærås wrote:
> what is N here? You're declaring it to be an int value in the template<> definition, and then use it as a type in the function definition.

Oops again :) Should've been typename N (where N is some integral type).

> Not exactly. range.front will assert after the last popFrontN (since the range is empty).

Ya, sorry, realized this a bit after I posted.

> It's trying to combine 3 and 4 I think, but it fails because this is allowed in D:
>
> int a;
> a = false;

Ah true, so it's more of a is(ElementType!R : bool) check?

>
> You'll want to pass the range as ref. hasLvalueElements checks if the elements have lvalue semantics

Doh, of course. It's in the name even!

> import std.range;
>
> struct R {
>     int[3] elements;
>     int index;
>
>     ref auto front() {
>         return elements[index];
>     }
>
>     void popFront() {
>         ++index;
>     }
>
>     bool empty() {
>         return index >= elements.length;
>     }
> }
>
> unittest {
>     assert(hasLvalueElements!(R));
>     auto a = R([1,2,3], 0);
>     auto b = a;
>     b.front = 4;
>     assert(a.elements == [1,2,3]);
>     assert(b.elements == [4,2,3]);
> }
>
> As we can see, b is a complete copy of a, and changing b does not change a. The exact same behavior would occur if a was passed by value to a function.
>
> --
>   Simen

Thanks for the input!


January 26, 2018
On Friday, 26 January 2018 at 14:35:25 UTC, Meta wrote:
> On Friday, 26 January 2018 at 14:16:04 UTC, aliak wrote:
>> 1) I've seen some phobos code checking for assignability like this:
>>
>>   is(typeof(range.front = false))
>>
>> ... is that an advantage of that over hasAssignableElements? Or is that just basically combining constraints 3 and 4 which I have above?
>
> Where did you see this? That's got to be some very old code; I can't think of any instance where you would not want to use `hasAssignableElements` instead.

Seems to occur in https://github.com/dlang/phobos/blob/v2.078.1/std/algorithm/mutation.d

eg: https://github.com/dlang/phobos/blob/v2.078.1/std/algorithm/mutation.d#L555


January 26, 2018
On Friday, 26 January 2018 at 15:33:03 UTC, aliak wrote:
> On Friday, 26 January 2018 at 14:35:25 UTC, Meta wrote:
>> On Friday, 26 January 2018 at 14:16:04 UTC, aliak wrote:
>>> 1) I've seen some phobos code checking for assignability like this:
>>>
>>>   is(typeof(range.front = false))
>>>
>>> ... is that an advantage of that over hasAssignableElements? Or is that just basically combining constraints 3 and 4 which I have above?
>>
>> Where did you see this? That's got to be some very old code; I can't think of any instance where you would not want to use `hasAssignableElements` instead.
>
> Seems to occur in https://github.com/dlang/phobos/blob/v2.078.1/std/algorithm/mutation.d
>
> eg: https://github.com/dlang/phobos/blob/v2.078.1/std/algorithm/mutation.d#L555

The function is called fill, and assigns a value to every element in the range. If a[0] = false compiles, we also want a.fill(false) to compile. It's simply testing that, rather than caring about the exact type of the elements.

--
  Simen
January 27, 2018
On Friday, 26 January 2018 at 23:15:41 UTC, Simen Kjærås wrote:
>
> The function is called fill, and assigns a value to every element in the range. If a[0] = false compiles, we also want a.fill(false) to compile. It's simply testing that, rather than caring about the exact type of the elements.
>
> --
>   Simen

I see. Yes that makes sense. Thank you.