25

Why this doesn't compile? I get compile error in 3 line

Cannot use T as reified type parameter. Use class instead

class Matrix2d<T>(val rows: Int, val cols: Int, init: (Int, Int) -> T) {

   var data = Array(rows * cols, { i ->
      val r = Math.floor(i.toDouble() / cols).toInt()
      init(r, i - r * cols)
   })

   operator fun get(row: Int, col: Int): T = data[row * cols + col]

   operator fun set(row: Int, col: Int, v: T) = {
      data[row * cols + col] = v
   }
}

Solution

I added a factory function which looks like a second constructor but implemented in inline function

class Matrix2d<T>(val rows: Int, val cols: Int, private val data: Array<T>) {

   companion object {
      operator inline fun <reified T> invoke(rows: Int, cols: Int, init: (Int, Int) -> T): Matrix2d<T> {
         return Matrix2d(rows, cols, Array(rows * cols, { i ->
            val r = Math.floor(i.toDouble() / cols).toInt()
            init(r, i - r * cols)
         }))
      }
   }

   init {
      if (rows * cols != data.size) throw IllegalArgumentException("Illegal array size: ${data.size}")
   }

   operator fun get(row: Int, col: Int): T = data[row * cols + col]

   operator fun set(row: Int, col: Int, v: T) {
      data[row * cols + col] = v
   }
}
Lancaster
  • 359
  • 1
  • 5
  • 8
  • 3
    Possible duplicate of [Kotlin NDArray with a lambda constructor with generic return type](http://stackoverflow.com/questions/35581867/kotlin-ndarray-with-a-lambda-constructor-with-generic-return-type) – mfulton26 Jan 30 '17 at 17:31

2 Answers2

26

JVM arrays, on which Kotlin arrays are mapped to, require the element type to be known at compile time to create an instance of array.

So you can instantiate Array<String> or Array<Any>, but not Array<T> where T is a type parameter, representing the type that is erased at compile time and hence is unknown. To specify that a type parameter must be known at compile time it is marked with reified modifier.

There are several options, what you can do in this situation:

  1. Use MutableList<T> for storing elements, which doesn't require reified T:

    // MutableList function, available in Kotlin 1.1
    val data = MutableList(rows * cols, { i ->
       val r = i / cols
       init(r, i % cols)
    })
    // or in Kotlin 1.0
    val data = mutableListOf<T>().apply {
        repeat(rows * cols) { i ->
            val r = i / cols
            add(init(r, i % cols))
        }
    }
    
  2. Create an array from an inline function with reified type parameter:

    inline fun <reified T> Matrix2d(val rows: Int, val cols: Int, init: (Int, Int) -> T) = 
        Matrix2d(rows, cols, Array(rows * cols, { .... })
    
    class Matrix2d<T> 
        @PublishedApi internal constructor(
            val rows: Int, val cols: Int,
            private val data: Array<T>
        ) 
    
  3. Use Array<Any?> as the storage, and cast its values to T in get function:

    val data = Array<Any?>(rows * cols, { .... })
    
    operator fun get(row: Int, col: Int): T = data[row * cols + col] as T
    
  4. Pass a parameter of type Class<T> or KClass<T> to constructor and use java reflection to create an instance of array.

Ilya
  • 21,871
  • 8
  • 73
  • 92
  • Thanks for explanation, I like the second way. I updated my post and code works properly, but now I have a public constructor in Matrix2d class which can be used by all, and it's confusing, because I have a third parameter as an array. And if I use that constructor I should pass the number of rows and columns. I wanted to specify contructor scope to private, but it's not available for inline function. How can I solve this problem in a different way? – Lancaster Jan 31 '17 at 11:12
  • 1
    Use internal constructor, though to call it from public inline function, you have to annotate it with @PublishedApi annotation (available in Kotlin 1.1). I'll update the example. – Ilya Jan 31 '17 at 15:09
3

Personally, the best workaround for me was:

@Suppress("UNCHECKED_CAST")
var pool: Array<T?> = arrayOfNulls<Any?>(initialCapacity) as Array<T?>
Riki137
  • 2,076
  • 2
  • 23
  • 26
  • This solution is just as viable as solution 3 in the selected answer; just probably should have private access. – Bryan W. Wagner Jan 15 '20 at 18:37
  • I don't think this works, it throws a ClassCastException with a misleading error message (see https://stackoverflow.com/questions/54536895/cannot-be-cast-to-class-because-they-are-in-unnamed-module-of-loader-app) but the fact still remains that it cannot do the cast – Emanuel Moecklin Sep 15 '21 at 16:05