swiftgenerics

Extension to a generic class in Swift, where the generic parameter is also generic


It's easier to explain the problem on an example. For instance, consider this simple generic class:

final class GenericClass<T> {
  private let parameter: T
  init(parameter: T) {
    self.parameter = parameter
  }
}

I want to add an empty init in an extension, when T is any Array (or even Collection, but it's not crucial for my case). I want something like this, but it doesn't compile:

// Reference to generic type 'Array' requires arguments in <...>
extension GenericClass where T: Array {
  convenience init() {
    // Cannot convert value of type '[Any]' to expected argument type 'T'
    self.init(parameter: [])
  }
}

How can I add a generic parameter to the Array? This syntax doesn't work:

extension GenericClass<U> where T: Array<U> { // Cannot find type 'U' in scope
  convenience init() {
    self.init(parameter: [U]()) // Cannot call value of non-function type '[Any]'
  }
}

(T == Array<U> doesn't work either.)

Is there any way to achieve what I described? I really would like to avoid declaring separate extensions for every element type I need (of course the code below works perfectly fine):

extension GenericClass where T == [Int] {
  convenience init() {
    self.init(parameter: [])
  }
}

Solution

  • If you specifically need Array, you can add the extra type parameter U to the init. instead of extension GenericClass<U>:

    extension GenericClass {
        convenience init<U>() where T == [U] {
            self.init(parameter: [])
        }
    }
    

    If all the init needs is to be able to convert [] to T. T can just be something that's ExpressibleByArrayLiteral.

    extension GenericClass where T: ExpressibleByArrayLiteral {
        convenience init() {
            self.init(parameter: [])
        }
    }
    

    If by [] you specifically mean "an empty collection", then you should require T to be RangeReplaceableCollection. This protocol provides an init that creates an empty collection.

    extension GenericClass where T: RangeReplaceableCollection {
        convenience init() {
            self.init(parameter: T())
        }
    }
    

    It is not the case that ExpressibleByArrayLiteral implies that the type is a collection. OptionSet for example, is not a Collection, but it is ExpressibleByArrayLiteral.