December 31, 2017
For me, front() should throw a pre-defined exception when called on an empty range in order to eliminate undefined behavior. It does take some time to make a check, but D does array bounds checking by default. Ideally the front() check could be turned off somehow ("-boundschecks=off") by the user for those who want maximum speed, but I guess there is no way to do that when using pre-compiled functions in a library.
December 31, 2017
On Sunday, 31 December 2017 at 01:03:17 UTC, Tony wrote:
> For me, front() should throw a pre-defined exception when called on an empty range in order to eliminate undefined behavior. It does take some time to make a check, but D does array bounds checking by default. Ideally the front() check could be turned off somehow ("-boundschecks=off") by the user for those who want maximum speed, but I guess there is no way to do that when using pre-compiled functions in a library.

That sounds like a good idea. Wouldn't the same apply to array bounds checking for precompiled functions though?

Also, is going out of array bounds well defined behavior in D even with boundscheck off? And any links to docs on UB in D?
December 31, 2017
On Sunday, 31 December 2017 at 13:14:10 UTC, aliak wrote:
> On Sunday, 31 December 2017 at 01:03:17 UTC, Tony wrote:
>> For me, front() should throw a pre-defined exception when called on an empty range in order to eliminate undefined behavior. It does take some time to make a check, but D does array bounds checking by default. Ideally the front() check could be turned off somehow ("-boundschecks=off") by the user for those who want maximum speed, but I guess there is no way to do that when using pre-compiled functions in a library.
>
> That sounds like a good idea. Wouldn't the same apply to array bounds checking for precompiled functions though?

Yeah, seems like the standard library must be doing one or the other (bounds checking array indexes or not bounds checking them) all the time, depending on how it was compiled.

> Also, is going out of array bounds well-defined behavior in D even with bounds check off?

I'm no expert, but I can't think of how it could be.

> And any links to docs on UB in D?

This thread was the first time I have heard it used with regard to D.


December 31, 2017
On 12/31/2017 02:14 PM, aliak wrote:
> Also, is going out of array bounds well defined behavior in D even with boundscheck off?

No. Without the checks you get undefined behavior. I.e., `-boundscheck=off` defeats the `@safe` attribute. For that reason, I'd advise against ever using it. Definitely don't think of it as a harmless optimization switch.
December 31, 2017
On Sunday, December 31, 2017 01:03:17 Tony via Digitalmars-d-learn wrote:
> For me, front() should throw a pre-defined exception when called on an empty range in order to eliminate undefined behavior. It does take some time to make a check, but D does array bounds checking by default. Ideally the front() check could be turned off somehow ("-boundschecks=off") by the user for those who want maximum speed, but I guess there is no way to do that when using pre-compiled functions in a library.

Except that the reason for arrays throwing RangeErrors when you try and index them out-of-bounds is to avoid memory safety issues, which is not necessarily the case at all when you're talking about ranges. Having ranges in general be checking empty in front, popFront, back, etc. would add unnecessary overhead - especially when you consider that ranges often wrap other ranges. You'd be layering on check after check when the calling code is already supposed to be checking empty when necessary to make sure that the range isn't empty. You'd even be layering checks on top of the checks that arrays already do, since many ranges are ultimately wrapped around a dynamic array at their core.

The extra checks on arrays avoid nasty memory-related problems and don't involve layering on extra checks all over the place, whereas ranges in general do not pose a memory safety problem (those that do should do checks like dynamic arrays do, but that's not the norm).

As for -boundschecks=off, that arguably shouldn't even exist. It can already be gotten around using the ptr property if you really want that extra speed, and the vast majority of programs shouldn't even consider using it, because skipping the bounds checking in arrays is just begging for memory corruption problems.

- Jonathan M Davis

December 31, 2017
On 12/30/2017 11:00 AM, aliak wrote:

> Instead of this:
>    auto result = range.op!f;
>    if (!result.empty) {
>      result.front.method();
>    }
> 
> This:
>    range.op!f.ifFront.method();
> 
> 
> In the above scenario I only want method to be called if the pipeline resulted in any element left in the range.

If you're fine with specifying the function as a template argument, the following works. (As seen with 's => s.foo()' below, you have to use a lambda for member functions anyway.)

auto ifFront(alias func, R)(R range) {
    import std.traits : isArray;
    static if (isArray!R) {
        import std.array : empty, front;
    }
    if (!range.empty) {
        func(range.front);
    }
}

unittest {
    size_t executed;

    struct S {
        size_t *e;
        this(ref size_t e) {
            this.e = &e;
        }

        void foo() {
            ++(*e);
        }
    }

    auto foo(int) {
        ++executed;
    }

    // Non-empty array; should be called
    auto a = [1];
    a.ifFront!foo;
    assert(executed == 1);

    // Empty array; should not be called
    int[] b;
    b.ifFront!foo;
    assert(executed == 1);

    // Non-empty S array; should be called
    auto c = [ S(executed) ];
    c.ifFront!(s => s.foo());
    assert(executed == 2);

    // Empty S array; should not be called
    S[] d;
    d.ifFront!(s => s.foo());
    assert(executed == 2);
}

void main() {
}

Ali
January 01, 2018
On Monday, 1 January 2018 at 04:18:29 UTC, Ali Çehreli wrote:
> If you're fine with specifying the function as a template argument, the following works. (As seen with 's => s.foo()' below, you have to use a lambda for member functions anyway.)
>
> Ali

Nice! Thanks :) And I think your usage for something named "ifFront" actually makes more sense than using it to return "saferef" functionality.

I've basically implemented an optional type for now and the "iffront" implementation looks like this:

import std.range: isInputRange;

auto iffront(Range)(Range r) if (isInputRange!Range) {
    import std.range: ElementType, empty, front;
    import optional: no, some;
    return r.empty ? no!(ElementType!Range) : some(r.front);
}

unittest {
    import std.algorithm: filter;
    assert([false].filter!"a".iffront.empty); // because optional is a range
}

unittest {
    import std.algorithm: filter;
    import optional: some, none;
    struct A {
        int f() {
            return 7;
        }
    }

    assert([A()].filter!"false".iffront.f == none);
    assert([A()].filter!"true".iffront.f == some(7));
}

And thanks everyone for the input. I'll play around with some of the ideas and see what comes of it.


January 01, 2018
On Monday, 1 January 2018 at 02:18:36 UTC, Jonathan M Davis wrote:
> Except that the reason for arrays throwing RangeErrors when you try and index them out-of-bounds is to avoid memory safety issues, which is not necessarily the case at all when you're talking about ranges. Having ranges in general be checking empty in front, popFront, back, etc. would add unnecessary overhead - especially when you consider that ranges often wrap other ranges. You'd be layering on check after check when the calling code is already supposed to be checking empty when necessary to make sure that the range isn't empty. You'd even be layering checks on top of the checks that arrays already do, since many ranges are ultimately wrapped around a dynamic array at their core.
>
> [...]

Makes sense. Especially after pointing out that ranges are mostly arrays at the end. Thanks!

1 2
Next ›   Last »