Thread overview
Why can't I pass a const array to a function that takes scope const arrays?
Feb 17, 2020
Adnan
Feb 17, 2020
Adnan
Feb 17, 2020
Adnan
Feb 17, 2020
Simen Kjærås
Feb 17, 2020
Adnan
Feb 17, 2020
Paul Backus
February 17, 2020
https://ideone.com/lVi5Uy

module strassens_matmul;

debug {
    static import std;
}

package {
    ulong getRowSize(T)(T[][] mat) {
        return mat[0].length;
    }

    ulong getColumnSize(T)(T[][] mat) {
        return mat.length;
    }

    T[][] createMatrix(T)(const ulong rowSize, const ulong columnSize) {
        return new T[][](rowSize, columnSize);
    }

    /// row and column are 0 index-based
    T* getPointPtr(T)(T[][] mat, const ulong row, const ulong column) {
        return &mat[row][column];
    }

    T getPointCopy(T)(T[][] mat, const ulong row, const ulong column) {
        return mat[row][column];
    }

    T[][] mulIterative(T)(scope const T[][] mat1, scope const T[][] mat2) {
        auto result = createMatrix!T(mat1.getRowSize, mat2.getColumnSize);
        foreach (row; 0 .. mat1.getRowSize()) {
            foreach (column; 0 .. mat2.getColumnSize()) {
                T value;
                foreach (i; 0 .. mat1.getRowSize()) {
                    value += mat1.getPointCopy(row, i) * mat2.getPointCopy(i, column);
                }
                *result.getPointPtr(row, column) = value;
            }
        }
        return result;
    }
}

unittest {
    const uint[][] matA = [[10, 20, 10], [4, 5, 6], [2, 3, 5]];
    const uint[][] matB = [[3, 2, 4], [3, 3, 9], [4, 4, 2]];
    const uint[][] matC = [[130, 120, 240], [51, 47, 73], [35, 33, 45]];
    assert(matA.mulIterative(matB) == matC);
}

///

source/strassens_matmul.d(30,42): Error: template strassens_matmul.getRowSize cannot deduce function from argument types !()(const(uint[][])), candidates are:
source/strassens_matmul.d(8,11):        getRowSize(T)(T[][] mat)
source/strassens_matmul.d(30,59): Error: template strassens_matmul.getColumnSize cannot deduce function from argument types !()(const(uint[][])), candidates are:
source/strassens_matmul.d(12,11):        getColumnSize(T)(T[][] mat)
source/strassens_matmul.d(31,43): Error: template strassens_matmul.getRowSize cannot deduce function from argument types !()(const(uint[][])), candidates are:
source/strassens_matmul.d(8,11):        getRowSize(T)(T[][] mat)
source/strassens_matmul.d(48,29): Error: template instance strassens_matmul.mulIterative!uint error instantiating
dmd failed with exit code 1.
February 17, 2020
On Monday, 17 February 2020 at 13:44:55 UTC, Adnan wrote:
> https://ideone.com/lVi5Uy
>
> module strassens_matmul;
>
> debug {
>     static import std;
> }
> ...

Okay I changed to
module strassens_matmul;

debug {
    static import std;
}

package {
    ulong getRowSize(T)(scope const T[][] mat) {
        return mat[0].length;
    }

    ulong getColumnSize(T)(scope const T[][] mat) {
        return mat.length;
    }

    T[][] createMatrix(T)(scope const ulong rowSize, scope const ulong columnSize) {
        return new T[][](rowSize, columnSize);
    }

    /// row and column are 0 index-based
    T* getPointPtr(T)(scope const ref T[][] mat, scope const ulong row, scope const ulong column) {
        return &mat[row][column];
    }

    T getPointCopy(T)(scope const ref T[][] mat, scope const ulong row, scope const ulong column) {
        return mat[row][column];
    }

    T[][] mulIterative(T)(scope const ref  T[][] mat1, scope const T[][] mat2) {
        auto result = createMatrix!T(getRowSize!T(mat1), getColumnSize!T(mat2));
        foreach (row; 0 .. mat1.getRowSize()) {
            foreach (column; 0 .. mat2.getColumnSize()) {
                T value;
                foreach (i; 0 .. mat1.getRowSize()) {
                    value += mat1.getPointCopy(row, i) * mat2.getPointCopy(i, column);
                }
                *result.getPointPtr(row, column) = value;
            }
        }
        return result;
    }
}

unittest {
    const uint[][] matA = [[10, 20, 10], [4, 5, 6], [2, 3, 5]];
    const uint[][] matB = [[3, 2, 4], [3, 3, 9], [4, 4, 2]];
    const uint[][] matC = [[130, 120, 240], [51, 47, 73], [35, 33, 45]];
    assert(matA.mulIterative(matB) == matC);
}

///

cdsa ~master: building configuration "cdsa-test-library"...
source/strassens_matmul.d(22,16): Error: cannot implicitly convert expression &mat[row][column] of type const(uint)* to uint*
source/strassens_matmul.d(37,36): Error: template instance strassens_matmul.getPointPtr!uint error instantiating
source/strassens_matmul.d(48,29):


February 17, 2020
On Monday, 17 February 2020 at 14:04:34 UTC, Adnan wrote:
> On Monday, 17 February 2020 at 13:44:55 UTC, Adnan wrote:
>> [...]
>
> Okay I changed to
> module strassens_matmul;
>
> [...]

I changed getPointPtr to following and now it works

    /// row and column are 0 index-based
    void assign(T)(ref T[][] mat, ulong row, ulong column, scope const T value) {
        mat[row][column] = value;
    }
February 17, 2020
On Monday, 17 February 2020 at 14:04:34 UTC, Adnan wrote:
> cdsa ~master: building configuration "cdsa-test-library"...
> source/strassens_matmul.d(22,16): Error: cannot implicitly convert expression &mat[row][column] of type const(uint)* to uint*
> source/strassens_matmul.d(37,36): Error: template instance strassens_matmul.getPointPtr!uint error instantiating
> source/strassens_matmul.d(48,29):

I'd just finished writing a long post explaining the stuff you've apparently figured out. Ah well. :p

In this case, getPointPtr return T*, but takes scope const ref T[][]. Since getPointPtr always takes a mutable array, you could just get rid of const on its parameters. Alternatively, if you want to be able to use it on arrays of different constness, you could use inout:

    inout(T)* getPointPtr(T)(inout T[][] mat, size_t row, size_t column) {

This will return a pointer to a mutable T if that's what the array holds when you call the function, const(T) if it's a const array, immutable(T) if it's immutable, and so on.

The same can be done with the other functions you have.

You are also somewhat overusing const, scope and ref, I'd say - you should not take an array by ref unless you plan on modifying it, which you are not doing in getPointPtr or any other of your functions. scope may be worth it, as it guarantees you won't be sending the data elsewhere.

None of these are necessary on your ulongs, which are passed by value and never attempted modified. If you really like the extra guarantee that you don't accidentally modify them, feel free to keep 'const', but 'scope' on a ulong does nothing, and it's been argued it should be a compiler error.

Lastly, you're using ulongs a lot, and this is mostly correct when compiling for 64-bit, but makes code fail to compile for 32-bit. Using size_t instead makes for code that works for both.

All in all, I end up with this code:

module strassens_matmul;

debug {
    static import std;
}

package {
    size_t getRowSize(T)(const T[][] mat) {
        return mat[0].length;
    }

    size_t getColumnSize(T)(const T[][] mat) {
        return mat.length;
    }

    T[][] createMatrix(T)(size_t rowSize, size_t columnSize) {
        return new T[][](rowSize, columnSize);
    }

    /// row and column are 0 index-based
    inout(T)* getPointPtr(T)(inout T[][] mat, size_t row, size_t column) {
        return &mat[row][column];
    }

    T getPointCopy(T)(const T[][] mat, size_t row, size_t column) {
        return mat[row][column];
    }

    T[][] mulIterative(T)(const T[][] mat1, const T[][] mat2) {
        auto result = createMatrix!T(getRowSize!T(mat1), getColumnSize!T(mat2));
        foreach (row; 0 .. mat1.getRowSize()) {
            foreach (column; 0 .. mat2.getColumnSize()) {
                T value;
                foreach (i; 0 .. mat1.getRowSize()) {
                    value += mat1.getPointCopy(row, i) * mat2.getPointCopy(i, column);
                }
                *result.getPointPtr(row, column) = value;
            }
        }
        return result;
    }
}

unittest {
    const uint[][] matA = [[10, 20, 10], [4, 5, 6], [2, 3, 5]];
    const uint[][] matB = [[3, 2, 4], [3, 3, 9], [4, 4, 2]];
    const uint[][] matC = [[130, 120, 240], [51, 47, 73], [35, 33, 45]];
    assert(matA.mulIterative(matB) == matC);
}

--
  Simen
February 17, 2020
On Monday, 17 February 2020 at 14:34:44 UTC, Simen Kjærås wrote:
> On Monday, 17 February 2020 at 14:04:34 UTC, Adnan wrote:
>> //
>
> All in all, I end up with this code:
>
> module strassens_matmul
> package {
>     T[][] mulIterative(T)(const T[][] mat1, const T[][] mat2) {
>         auto result = createMatrix!T(getRowSize!T(mat1), getColumnSize!T(mat2));
>         foreach (row; 0 .. mat1.getRowSize()) {
>             foreach (column; 0 .. mat2.getColumnSize()) {
>                 T value;
>                 foreach (i; 0 .. mat1.getRowSize()) {
>                     value += mat1.getPointCopy(row, i) * mat2.getPointCopy(i, column);
>                 }
>                 *result.getPointPtr(row, column) = value;
>             }
>         }
>         return result;
>     }
> }

Aren’t these passing by values? That’s why I used ref. Plus since I don’t want to change the arrays in the function scope I used const scope.


February 17, 2020
On Monday, 17 February 2020 at 20:08:24 UTC, Adnan wrote:
> On Monday, 17 February 2020 at 14:34:44 UTC, Simen Kjærås wrote:
>> On Monday, 17 February 2020 at 14:04:34 UTC, Adnan wrote:
>>> //
>>
>> All in all, I end up with this code:
>>
>> module strassens_matmul
>> package {
>>     T[][] mulIterative(T)(const T[][] mat1, const T[][] mat2) {
>>         auto result = createMatrix!T(getRowSize!T(mat1), getColumnSize!T(mat2));
>>         foreach (row; 0 .. mat1.getRowSize()) {
>>             foreach (column; 0 .. mat2.getColumnSize()) {
>>                 T value;
>>                 foreach (i; 0 .. mat1.getRowSize()) {
>>                     value += mat1.getPointCopy(row, i) * mat2.getPointCopy(i, column);
>>                 }
>>                 *result.getPointPtr(row, column) = value;
>>             }
>>         }
>>         return result;
>>     }
>> }
>
> Aren’t these passing by values? That’s why I used ref. Plus since I don’t want to change the arrays in the function scope I used const scope.

In D, the type `T[]` is not an array like in C, but a slice, containing a pointer to some data and an integer indicating the array's length. So when you pass it to a function by value, only the pointer and length are copied, not the data itself.

You can read more about D's slices in this article: https://dlang.org/articles/d-array-article.html