Swift 可选提升与通用重载决议

Swift optional promotion vs generic overload resolution

请考虑以下代码:

protocol P {}
class X {}
class Y: P {}

func foo<T>(_ closure: (T) -> Void) { print(type(of: closure)) }
func foo<T>(_ closure: (T) -> Void) where T: P { print(type(of: closure)) }

let xClosure: (X?) -> Void = { _ in }
foo(xClosure)   //  prints "(Optional<X>) -> ()"
let yClosure: (Y?) -> Void = { _ in }
foo(yClosure)   //  prints "(Y) -> ()"

为什么 foo(yClosure) 调用解析为 foo 的版本限制为 T: P? 我明白为什么那个版本会打印它打印的内容, 我没有看到的是为什么它被调用而不是另一个。

对我来说,非 P 版本似乎更适合 T == (Y?) -> Void。 当然,约束版本更具体,但需要转换 (从 (Y?) -> Void(Y) -> Void 的隐式转换), 而非P版本可以调用,无需转换。

有没有办法修复此代码,以便调用 P 约束版本 只有传入的闭包的参数类型直接符合P, 没有任何隐式转换?

根据我的实验,特异性似乎总是​​胜过方差转换。例如:

func bar<T>(_ x: [Int], _ y: T) { print("A") }
func bar<T: P>(_ x: [Any], _ y: T) { print("B") }

bar([1], Y()) // A

bar 更具体,但需要从 [Int][Any].

的方差转换

为什么可以从 (Y?) -> Void 转换为 (P) -> Void,请参阅 。请注意,YY? 的子类型,这是编译器的魔法。

既然如此一致,这种行为似乎是设计使然。因为你不能真正让 Y not 成为 Y? 的子类型,所以如果你想获得所需的行为,你没有太多选择。

我有这个解决方法,我承认它真的很难看 - 制作您自己的 Optional 类型。我们称它为 Maybe<T>:

enum Maybe<T> {
    case some(T)
    case none

    // implement all the Optional methods if you want
}

现在,您的 (Maybe<Y>) -> Void 不会转换为 (P) -> Void。通常我不会推荐这个,但既然你说:

in the real-world code where I encountered this, the closure has multiple params, any of them can be optional, so this would lead to a combinatorial explosion.

我认为重塑 Optional 可能 是值得的。