Swift Generics:无法将类型的值转换为预期的参数类型

时间:2017-07-08 22:01:14

标签: swift generics

这是我的代码:

protocol SomeProtocol {
}

class A: SomeProtocol {
}

func f1<T: SomeProtocol>(ofType: T.Type, listener: (T?) -> Void) {
}

func f2<T: SomeProtocol>(ofType: T.Type, listener: ([T]?) -> Void) {
}

func g() {
    let l1: (SomeProtocol?) -> Void = ...
    let l2: ([SomeProtocol]?) -> Void = ...
    f1(ofType: A.self, listener: l1) // NO ERROR
    f2(ofType: A.self, listener: l2) // COMPILE ERROR: Cannot convert value of type '([SomeProtocol]?) -> Void' to expected argument type '([_]?) -> Void'
}

第二个闭包有一个泛型类型对象的数组的参数有什么问题?

3 个答案:

答案 0 :(得分:7)

Swift 4.1更新

这是一个已修复的错误in this pull request,它将使其成为Swift 4.1的版本。您的代码现在可以在4.1快照中按预期编译。

Pre Swift 4.1

这看起来好像你只是把编译器拉得太远了。

  • 它可以处理从子类型元素数组到超类型元素数组的转换,例如[A][SomeProtocol] - 这是协方差。值得注意的是,数组在这里始终是一个边缘情况,因为任意泛型是不变的。某些集合,例如Array,仅get special treatment from the compiler允许协方差。

  • 它可以处理具有超类型参数的函数到具有子类型参数的函数的转换,例如(SomeProtocol) -> Void(A) -> Void - 这是逆转。

然而,它似乎无法一次性完成(但实际上它应该能够; file a bug随意)。

对于它的价值,这与泛型无关,以下再现了相同的行为:

protocol SomeProtocol {}
class A : SomeProtocol {}

func f1(listener: (A) -> Void) {}
func f2(listener: ([A]) -> Void) {}
func f3(listener: () -> [SomeProtocol]) {}

func g() {

    let l1: (SomeProtocol) -> Void = { _ in }        
    f1(listener: l1) // NO ERROR

    let l2: ([SomeProtocol]) -> Void = { _ in }
    f2(listener: l2) 
    // COMPILER ERROR: Cannot convert value of type '([SomeProtocol]) -> Void' to
    // expected argument type '([A]) -> Void'

    // it's the same story for function return types
    let l3: () -> [A] = { [] }
    f3(listener: l3)
    // COMPILER ERROR: Cannot convert value of type '() -> [A]' to
    // expected argument type '() -> [SomeProtocol]'
}

在修复之前,在这种情况下,一个解决方案是简单地使用闭包表达式作为两种函数类型之间的蹦床:

// converting a ([SomeProtocol]) -> Void to a ([A]) -> Void.
// compiler infers closure expression to be of type ([A]) -> Void, and in the
// implementation, $0 gets implicitly converted from [A] to [SomeProtocol].
f2(listener: { l2($0) })

// converting a () -> [A] to a () -> [SomeProtocol].
// compiler infers closure expression to be of type () -> [SomeProtocol], and in the
// implementation, the result of l3 gets implicitly converted from [A] to [SomeProtocol]
f3(listener: { l3() })

并且,适用于您的代码:

f2(ofType: A.self, listener: { l2($0) })

这是有效的,因为编译器将闭包表达式推断为([T]?) -> Void类型,可以将其传递给f2。在闭包的实现中,编译器然后执行$0[T]?[SomeProtocol]?的隐式转换。

而且,as Dominik is hinting at,这个蹦床也可以作为f2的额外重载来完成:

func f2<T : SomeProtocol>(ofType type: T.Type, listener: ([SomeProtocol]?) -> Void) {
    // pass a closure expression of type ([T]?) -> Void to the original f2, we then
    // deal with the conversion from [T]? to [SomeProtocol]? in the closure.
    // (and by "we", I mean the compiler, implicitly)
    f2(ofType: type, listener: { (arr: [T]?) in listener(arr) })
}

允许您再次将其称为f2(ofType: A.self, listener: l2)

答案 1 :(得分:2)

func f2<T: SomeProtocol>(ofType: T.Type, listener: ([T]?) -> Void) {...}中的侦听器闭包要求其参数为T的数组,其中T是实现SomeProtocol的类型。通过编写<T: SomeProtocol>,您强制执行该数组的所有元素属于同一类型。

比方说,您有两个类:AB。两者都完全不同。然而,两者都实施SomeProtocol。在这种情况下,由于类型约束,输入数组不能是[A(), B()]。输入数组可以是[A(), A()][B(), B()]

但是,当您将l2定义为let l2: ([SomeProtocol]?) -> Void = ...时,您允许闭包接受诸如[A(), B()]之类的参数。因此,此闭包和您在f2中定义的闭包是不兼容的,编译器无法在两者之间进行转换。

很遗憾,您无法将类型强制执行添加到l2变量,如here所述。您可以做的是,如果您知道l2将对类A的数组起作用,您可以按如下方式重新定义它:

let l2: ([A]?) -> Void = { ... }

让我试着用一个更简单的例子解释一下。让我们假设您编写了一个通用函数来查找可比较数组中的最大元素:

func greatest<T: Comparable>(array: [T]) -> T {
    // return greatest element in the array
}

现在,如果您尝试调用该函数,那么:

let comparables: [Comparable] = [1, "hello"]
print(greatest(array: comparables))

编译器会抱怨,因为无法比较Int和String。你必须做的是:

let comparables: [Int] = [1, 5, 2]
print(greatest(array: comparables))

答案 2 :(得分:0)

对哈米什的答案一无所知,他100%正确。但是如果你想要超级简单的解决方案而没有任何解释或代码正常工作,那么在使用泛型协议数组时,请使用:

func f1<T: SomeProtocol>(ofType: T.Type, listener: (T?) -> Void) {
}

func f2<Z: SomeProtocol>(ofType: Z.Type, listener: ([SomeProtocol]?) -> Void) {
}