Swift中的通用方法重写不起作用

7

有一个 Printable 协议和一个来自第三方的 Printer 结构体。

protocol Printable {}

struct Printer {

    static func print<T>(object: T) -> String {
        return "T"
    }

    static func print<T: Printable>(object: T) -> String {
        return "Printable"
    }

}

现在我正在制作一个通用的
struct Generic<T> {

    var args: T

    func display() {
        print(Printer.print(args))
    }

}

和两个结构体

struct Obj {}
struct PrintableObj: Printable {}
var obj = Generic(args: Obj())
var printableObj = Generic(args: PrintableObj())

当我调用它们的显示函数时。
obj.display()

显示 T

printableObj.display()

显示 T 但我希望它打印出 "Printable"

我能想到的一个解决方案是有两种不同的泛型。

struct Generic<T>
struct PrintableGeneric<T: Printable>

有没有其他解决方案,而不需要改变Printable协议和Printer结构。
3个回答

2
static func print<T>(object: T) -> String {
    if object is Printable {
        return "Printable"
    } else {
        return "T"
    }
}

谢谢。但是那段代码来自另一个源,我无法修改它。我只能在通用部分进行更改。 - Rahul Katariya

1
在我看来,你唯一的选择就是在“print()”函数中使用if-else和类型转换。
static func print<T>(object: T) -> String {
    if let _ = object as? Printable {
        return "Printable"
    }
    return "T"
}

或非通用变量。
static func print(object: Any) -> String {
    if let _ = object as? Printable {
        return "Printable"
    }
    return "T"
}

谢谢。但是那段代码来自另一个源,我无法修改它。我只能在通用部分进行更改。 - Rahul Katariya

1

是的,但答案有点奇怪。第一部分还算有道理;第二部分就完全奇怪了。让我们来逐步分析一下。

struct Generic<T> {
    var args: T
    func display() {
        print(Printer.print(args))
    }
}

正确的重载函数在编译时确定,而不是运行时。这是最让人困惑的事情。他们希望像JavaScript一样处理Swift,其中一切都是动态的。Swift 喜欢静态,因为它可以确保你的类型正确,并且它可以进行许多优化(而Swift非常喜欢进行编译器优化)。那么,在编译时,args 的类型是什么?嗯,它是 T。是否已知 TPrintable?不是。因此,使用非 Printable 版本。
但是当Swift使用PrintableObj专门化泛型时,它不知道此时它是否为Printable吗?编译器能否在此时创建不同版本的display函数?是的,如果我们在编译时知道该函数将存在的所有调用者,并且它们中没有一个会被扩展为Printable(这可能发生在完全不同的模块中)。解决这个问题很难,因为这会创建许多奇怪的角落案例(例如,内部事物的行为与公共事物不同),并且不会强制Swift主动生成某些未来调用者可能需要的每个可能的version of display。Swift可能会在未来改进,但我认为这是一个难题。(Swift已经遭受了一些性能降低,以便公共泛型可以在没有访问原始源代码的情况下进行专门化。这将使问题变得更加复杂。)
好的,我们明白了。T不是Printable。但是,如果我们有一个已知于编译时且位于此函数内部的明确可打印类型,那么它会起作用吗?
func display() {
    if let p = args as? Printable {
        print(Printer.print(p))
    } else {
        print(Printer.print(args))
    }
}

非常接近……但还不够。这个几乎有效。实际上,if-let正是你想要的。 p被赋值了。它是Printable。但它仍然调用非Printable函数。?!?!?

我个人认为这是Swift当前存在的问题,并且很希望它能得到解决。这甚至可能是一个bug。问题在于Printable本身不符合Printable。是的,我也不明白,但就是这样。因此,我们需要创建一个符合Printable的东西来获得正确的重载。像往常一样,type erasers来拯救。

struct AnyPrintable: Printable {
    let value: Printable
}

struct Generic<T> {
    var args: T

    func display() {
        if let p = args as? Printable {
            print(Printer.print(AnyPrintable(value: p)))
        } else {
            print(Printer.print(args))
        }
    }
}

这将以您想要的方式打印。(假设Printable需要一些方法,您只需将这些方法添加到AnyPrintable类型擦除器即可。)
当然,在Printer中不应以这种方式使用通用重载。它太令人困惑和脆弱了。看起来很好,但总是出问题。

非常感谢您提供的信息。看起来很复杂,不太适合我现在正在尝试做的事情。我正在努力让GenericResponseString和GenericResponsePerson测试通过。 - Rahul Katariya
祝好运...我在这些超聪明/神奇的方法中进行了很多实验,以解决基本问题。个人认为,一些简单代码的几行,即使偶尔需要在这里或那里重复几行乏味的代码,也能更好地工作,特别是在调试时(我现在只需使用guard-let来解析JSON;如此容易)。虽然不太令人兴奋,但在我的项目中效果更好。但对于那些仍在探索前沿的人们,祝你们好运!这很有趣,但也会有很多像这样的挫折。 - Rob Napier

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接