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.".
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]
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>
.