I think I figured this one out.

Before 2.066, we did not have proper support for multi-dimensional slicing. The following were the semantics we had:

-- The opIndex() overloads provided access to direct elements. Since multi-dimensional support was incomplete, opIndex() was about accessing a single object:

    a[i]     // element i by opIndex(size_t)
    a[i, j]  // element i,j by opIndex(size_t, size_t)

-- The two overloads of opSlice() returned range objects that represented either all of the elements or a slice of them:

    a[]        // opSlice()
    a[i..j]    // opSlice(size_t, size_t)

Note that those features are still usable but according to documentation, they are discouraged.

Since 2.066, we have multi-dimensional slicing. The way we look at these operators have changed a little:

-- The opIndex() overloads now return ranges that represent a range of elements. For example, if 'a' is a two-dimensional matrix, the first line below should return a sub-matrix inside it, not a single element:

    a[i..j, k..l]        // opIndex(MyRange, MyRange)

The following can indeed return a single element but I think it is a valid design decision to return a sub-matrix consisting of a single element as well:

    a[i, j]    // a matrix of one element by opIndex(size_t size_t)

A single matrix row:

    a[i, j..j]           // opIndex(size_t, MyRange)

Here is the answer to my original question: Consistent with the above, now opIndex() must take the responsibility of returning all of the elements:

    a[]                   // opIndex()

-- With that change of responsibility, what remains for opSlice() is the only task of producing a range object that opIndex() can later use to represent one or more elements:

    a[i..j, k..l]  // opIndex(opSlice(i, j), opSlice(k, l))

In summary, the following is what opSlice() should do almost in all cases:

    Tuple!(size_t size_t) opSlice(size_t beg, size_t end)
    {
        return tuple(beg, end);
    }

Also note that representing i..j and k..l can always be done by a Tuple!(size_t, size_t) without loss of any information. (i.e. MyRange above can actually be Tuple!(size_t, size_t)):

I am attaching an example that helped me understand what was going on. Note that the program is decidedly "elegant" as opposed to "efficient". :) For example, even initializing a single element by random access goes through these steps:

Initializing 1 of 80
deneme.Matrix.opIndexAssign!(int, int).opIndexAssign
deneme.Matrix.opIndex!(int, int).opIndex
deneme.Matrix.opDollar!0LU.opDollar
deneme.Matrix.opDollar!1LU.opDollar
deneme.Matrix.subMatrix
deneme.Matrix.this
deneme.Matrix.opAssign
[...]

But I like it. :)

Ali

import std.stdio;
import std.format;
import std.string;

struct Matrix
{
private:

    int[][] rows;

    /* Represents a range of rows of columns. */
    struct Range
    {
        size_t beg;
        size_t end;
    }

    /* Returns a reference to a sub-matrix that correspond to
     * the range arguments. */
    Matrix subMatrix(Range rowRange, Range columnRange)
    {
        writeln(__FUNCTION__);

        int[][] slices;

        foreach (row; rows[rowRange.beg .. rowRange.end]) {
            slices ~= row[columnRange.beg .. columnRange.end];
        }

        return Matrix(slices);
    }

public:

    this(size_t height, size_t width)
    {
        writeln(__FUNCTION__);

        rows = new int[][](height, width);
    }

    this(int[][] rows)
    {
        writeln(__FUNCTION__);

        this.rows = rows;
    }

    void toString(void delegate(const(char)[]) sink) const
    {
        formattedWrite(sink, "%(%(%5s %)\n%)", rows);
    }

    /* Assigns the value to all of the elements of the
     * matrix. */
    Matrix opAssign(int value)
    {
        writeln(__FUNCTION__);

        foreach (row; rows) {
            row[] = value;
        }

        return this;
    }

    /* Applies the operation to each element and assigns the
     * result back to it. e.g. 'm += 42'*/
    Matrix opOpAssign(string op)(int value)
    {
        writeln(__FUNCTION__);

        foreach (row; rows) {
            mixin ("row[] " ~ op ~ "= value;");
        }

        return this;
    }

    /* Returns the size of the provided dimension. */
    size_t opDollar(size_t dimension)() const
    {
        writeln(__FUNCTION__);

        static if (dimension == 0) {
            return rows.length;

        } else static if (dimension == 1) {
            return rows.length ? rows[0].length : 0;

        } else {
            static assert(false,
                          format("Invalid dimension: %s",
                                 dimension));
        }
    }

    /* Returns a range representing the provided indices. */
    Range opSlice(size_t dimension)(size_t beg, size_t end)
    {
        writeln(__FUNCTION__);

        return Range(beg, end);
    }

    /* Returns a sub-matrix corresponding to the arguments. */
    Matrix opIndex(A...)(A args)
    {
        writeln(__FUNCTION__);

        Range[2] ranges = [ Range(0, opDollar!0),
                            Range(0, opDollar!1) ];

        foreach (i, a; args) {
            static if (is (typeof(a) == Range)) {
                ranges[i] = a;

            } else static if (is (typeof(a) : size_t)) {
                ranges[i] = Range(a, a + 1);

            } else {
                static assert(false,
                              format("Invalid index: %s",
                                     typeof(a).stringof));
            }
        }

        return subMatrix(ranges[0], ranges[1]);
    }

    /* Assigns the value to each element of the sub-matrix
     * determined by the arguments. */
    Matrix opIndexAssign(A...)(int value, A args)
    {
        writeln(__FUNCTION__);

        Matrix subMatrix = opIndex(args);
        return subMatrix = value;
    }

    /* Applies the operation to each element of the
     * sub-matrix. e.g. 'm[i, j] += 42'*/
    Matrix opIndexOpAssign(string op, A...)(int value, A args)
    {
        writeln(__FUNCTION__);

        Matrix subMatrix = opIndex(args);
        mixin ("return subMatrix " ~ op ~ "= value;");
    }
}

/* Applies the provided code and prints the result as well as
 * the new state of the matrix. */
void apply(string code)(Matrix m)
{
    writefln("\n--- %s ---", code);
    mixin ("auto result = (" ~ code ~ ");");
    writefln("result:\n%s", result);
    writefln("m:\n%s", m);
}

void main()
{
    enum height = 10;
    enum width = 8;

    auto m = Matrix(height, width);

    int counter = 0;
    foreach (column; 0 .. height) {
        foreach (row; 0 .. width) {
            writefln("Initializing %s of %s",
                     counter + 1, height * width);

            m[column, row] = counter;
            ++counter;
        }
    }

    writeln(m);

    apply!("m[1, 1] = 42")(m);
    apply!("m[0, 1 .. $] = 43")(m);
    apply!("m[0 .. $, 3] = 44")(m);
    apply!("m[$-4 .. $-1, $-4 .. $-1] = 7")(m);

    apply!("m[1, 1] *= 2")(m);
    apply!("m[0, 1 .. $] *= 4")(m);
    apply!("m[0 .. $, 0] *= 10")(m);
    apply!("m[$-4 .. $-2, $-4 .. $-2] -= 666")(m);

    apply!("m[1, 1]")(m);
    apply!("m[2, 0 .. $]")(m);
    apply!("m[0 .. $, 2]")(m);
    apply!("m[0 .. $ / 2, 0 .. $ / 2]")(m);

    apply!("++m[1..3, 1..3]")(m);
    apply!("--m[2..5, 2..5]")(m);

    apply!("m[]")(m);
    apply!("m[] = 20")(m);
    apply!("m[] /= 4")(m);
    apply!("(m[] += 5) /= 10")(m);
}

Reply via email to