Zhu Shengqi
Zhu Shengqi

Reputation: 3802

How to call a generic function with parameter of associated type in Swift 3

Here's my sample code:

protocol P {

}

protocol B {

  associatedtype ID: P

}

class MyClass: B {
  enum ID: P {
    case one
    case two
  }
}

func process<T: B>(_ value: T.ID) {
  // do something
}

process(MyClass.ID.one) // Compile Error: cannot convert value of type 'MyClass.ID' to expected argument type '_.ID'

As you can see, I define a generic function with a parameter whose type is an associated type of the generic type T. How can I call this function? I want to use MyClass.ID.one as the argument, but the compiler gives the following warning:

cannot convert value of type 'MyClass.ID' to expected argument type '_.ID'

Upvotes: 3

Views: 3385

Answers (2)

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726987

It appears that Swift has a problem inferring T from the invocation by starting from MyClass.ID being T.ID and backing into MyClass being T.

If you change your function to take an additional parameter of type T, the code compiles and runs fine:

func process<T: B>(_ value: T.ID, _ workaround : T) {
  // do something
}

let workaround = MyClass()
process(MyClass.ID.one, workaround)

Swift designers could approach this inference problem in two ways:

  • Fix inference engine to allow for this use case, or
  • Make it easier for the inference engine to figure out T by enforcing stricter rules on function signatures.

It appears that they decided on the second approach with Swift 4, because the original function fails to compile, issuing the following error:

generic parameter 'T' is not used in function signature

A better work-around is to pass the type instead of an instance, using Java-style approach:

func process<T: B>(_ value: T.ID, _ workaround: T.Type) {
  // do something
}
...
process(MyClass.ID.one, MyClass.self)

Note: The edit is based on very insightful comments by tesch

Upvotes: 7

Michael Fourre
Michael Fourre

Reputation: 3015

dasblinkenlight provides a good answer to why your current implementation doesn't work. If the decided resolution is to simply find another way to implement this function, then I would opt for a less hacky solution:

extension B {
    static func process(_ value: Self.ID) {
        //do stuff
    }
}

MyClass.process(.one)

This takes advantage of the more flexible type inference achievable via protocol extension Self.

Upvotes: 0

Related Questions