(There is an honest question in the end, please read on.)

All good reasons set aside, both in favour and against 0-based arrays, the only reason that is relevant to me right now is that we are seriously looking into translating close to a million lines of foreign code to D, from a language that supports arrays over arbitrary ranges of indices (positive and negative). Adapting this much program logic to a different array base is out of the question; this is an engineering application and lives are at stake. So let's not bring the base-arguments back into this sub-thread but focus on a performant solution.

Expanding on Marc's outset, I now have:

/*****************
* A fixed-length array with an index that runs from $(D_PARAM first)
 * to $(D_PARAM last) inclusive.
 *
 * Indices are converted, which involves a small overhead.
 */
struct StaticArray(T, ptrdiff_t first, ptrdiff_t last) {
    T[last - first + 1] _payload;

    alias _payload this;

    // Support e = arr[5];
    ref T opIndex(ptrdiff_t index) {
        assert(index >= first);
        assert(index <= last);
        return _payload[index - first];
    }

    // Support arr[5] = e;
    void opIndexAssign(U : T)(auto ref U value, ptrdiff_t index) {
        assert(index >= first);
        assert(index <= last);
        _payload[index - first] = value;
    }

    // Support foreach(e; arr).
    int opApply(scope int delegate(ref T) dg)
    {
        int result = 0;

        for (int i = 0; i < _payload.length; i++)
        {
            result = dg(_payload[i]);
            if (result)
                break;
        }
        return result;
    }

    // Support foreach(i, e; arr).
    int opApply(scope int delegate(ptrdiff_t index, ref T) dg)
    {
        int result = 0;

        for (int i = 0; i < _payload.length; i++)
        {
            result = dg(i + first, _payload[i]);
            if (result)
                break;
        }
        return result;
    }

    // Write to binary file.
    void toFile(string fileName)
    {
        import std.stdio;
        auto f = File(fileName, "wb");
        if (f.tryLock)
        {
            f.rawWrite(_payload);
        }
    }
}

unittest {
    StaticArray!(int, -10, 10) arr;
    assert(arr.length == 21);

    foreach (ref e; arr)
        e = 42;
    assert(arr[-10] == 42);
    assert(arr[0]   == 42);
    assert(arr[10]  == 42);

    foreach (i, ref e; arr)
        e = i;
    assert(arr[-10] == -10);
    assert(arr[0]   ==   0);
    assert(arr[5]   ==   5);
    assert(arr[10]  ==  10);

    arr[5] = 15;
    assert(arr[5]   == 15);
}

//////////////

(The first and last indices probably don't need to be template arguments, they could possibly be immutable members of the struct; But that is not what worries me now.) The thing is that a small overhead is paid in opIndex() and opIndexAssign() (the other member functions are fine). I'd like to get rid of that overhead.

In "Numerical Recipes in C", section 1.2, Press et al. propose an easy solution using an offset pointer:

float b[4], *bb;
bb = b - 1;

Thus bb[1] through bb[4] all exist, no space is wasted nor is there a run-time overhead.

I have tried to adapt the internals of my struct to Press' approach, but it seems to me it is not that easy in D -- or I'm just not proficient enough. Can someone here show me how that could be done?

Thanks!

Reply via email to