实现具有不同关联类型的协议

implement protocol with different associated type

我有一个协议我的 swift 代码库我有一个带有关联类型和两个方法的协议。这两种方法都为协议的关联类型定义了不同的通用约束。我想让结构符合两个协议,但有两个不同的关联类型。

protocol Convertable {
    associatedtype TargetType
    func convert() -> TargetType
}

func show<T : Convertable where T.TargetType == String>(toShow : T) {
    print(toShow.convert())
}
func add<T : Convertable where T.TargetType == Int>(a : T, b : T) -> Int {
    return a.convert() + b.convert()
}

struct MyData {
    var data : Int
}

作为扩展,我使结构符合 TargetType 将是 String 的协议,以便将其传递给 show 方法:

extension MyData : Convertable {
    func convert() -> String { return String(self.data) }
}

到目前为止一切正常。但现在我也希望在 TargetType 绑定到 Int 时让结构符合 Convertable 协议。哪个似乎是不可能的?

我尝试的第一件事是将转换方法的第二个定义添加到扩展中:

extension MyData : Convertable {
    func convert() -> String { return String(self.data) }
    func convert() -> Int { return data }
}

编译器现在抱怨 MyData 不再符合协议。其次是将其拆分为两个扩展并明确绑定 TargetType。

extension MyData : Convertable {
    typealias TargetType = Int
    func convert() -> Int { return data }
}
extension MyData : Convertable {
    typealias TargetType = String
    func convert() -> String { return String(data) }
}

这导致编译器现在抱怨 TargetType 被重新定义。

我最后的尝试是定义两个扩展 Convertable 协议并约束 TargetType 的协议,然后通过扩展实现它们:

protocol ConvertableString : Convertable {
    associatedtype TargetType = String
}
protocol ConvertableInt : Convertable {
    associatedtype TargetType = Int
}

extension MyData : ConvertableInt {
    func convert() -> Int { return self.data }
}
extension MyData : ConvertableString {
    func convert() -> String { return String(self.data) }
}

这让编译器现在对扩展感到满意,但不再对 show 的调用感到满意,因为它不知道它可以使用 MyData.

调用函数

有没有我监督过的事情,或者目前在 swift 中不可能做到这一点?

我只是资助了一种存档方法。诀窍是在协议的一个子类型中添加另一个关联类型:

protocol ConvertableInt : Convertable {
    associatedtype TResI
    typealias TargetType = TResI
}

extension MyData : Convertable {
    typealias TargetType = String
    func convert() -> String { return String(self.data) }
}

extension MyData : ConvertableInt {
    typealias TResI = Int
    func convert() -> TResI { return self.data }
}

这也允许去掉字符串的第二个子类型。

虽然这通过了编译器,但它在运行时完全崩溃了!

编译器总是调用显式 typealias 定义的方法。在这种情况下:

typealias TargetType = String

这将导致将地址解释为整数并给您完全错误的结果。如果你反之定义它,它只会崩溃,因为它试图将整数解释为地址。