14

Suppose I have a generic class and I need a 2D array of generic type T. If I try the following

class Matrix<T>(width: Int, height: Int) {
    val data: Array<Array<T>> = Array(width, arrayOfNulls<T>(height))
}

the compiler will throw an error saying "Cannot use 'T' as reified type parameter. Use a class instead.".

2 Answers 2

11

Just because the syntax has moved on a bit, here's my take on it:

class Array2D<T> (val xSize: Int, val ySize: Int, val array: Array<Array<T>>) {

    companion object {

        inline operator fun <reified T> invoke() = Array2D(0, 0, Array(0, { emptyArray<T>() }))

        inline operator fun <reified T> invoke(xWidth: Int, yWidth: Int) =
            Array2D(xWidth, yWidth, Array(xWidth, { arrayOfNulls<T>(yWidth) }))

        inline operator fun <reified T> invoke(xWidth: Int, yWidth: Int, operator: (Int, Int) -> (T)): Array2D<T> {
            val array = Array(xWidth, {
                val x = it
                Array(yWidth, {operator(x, it)})})
            return Array2D(xWidth, yWidth, array)
        }
    }

    operator fun get(x: Int, y: Int): T {
        return array[x][y]
    }

    operator fun set(x: Int, y: Int, t: T) {
        array[x][y] = t
    }

    inline fun forEach(operation: (T) -> Unit) {
        array.forEach { it.forEach { operation.invoke(it) } }
    }

    inline fun forEachIndexed(operation: (x: Int, y: Int, T) -> Unit) {
        array.forEachIndexed { x, p -> p.forEachIndexed { y, t -> operation.invoke(x, y, t) } }
    }
}

This also allows you to create 2d arrays in a similar manner to 1d arrays, e.g. something like

val array2D = Array2D<String>(5, 5) { x, y -> "$x $y" }

and access/set contents with the indexing operator:

val xy = array2D[1, 2]
Sign up to request clarification or add additional context in comments.

1 Comment

It's a nice solution, but it's a bit faulty. Because it's set array property via public constructor and set xSize and ySize at the same time. It could be misleading.
9

The problem is calling arrayOfNulls<T>(height) with the non-reified type parameter T. But we also can't make T reified, the compiler will throw the following error: "Only type parameters of inline functions can be reified"

So that's what we're going to do. Instead of the constructor we use an inlined factory method:

class Matrix<T> private(width: Int, height: Int, arrayFactory: (Int) -> Array<T>) {

    class object {
        inline fun <reified T>invoke(width: Int, height: Int)
                = Matrix(width, height, { size -> arrayOfNulls<T>(size) })
    }

    val data: Array<Array<T>> = Array(width, { size -> arrayFactory(size) })
}

Notice, the constructor is now private, so calling Matrix() will correctly call the new invoke() method (related question). Because the method is inlined, we can use reified generics which makes it possible to call arrayOfNulls<T>.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.