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但我希望它打印“可打印”
我能想到的一个解决方案是拥有两种不同的泛型
struct Generic<T> struct PrintableGeneric<T: Printable>
是否有任何其他解决方案,而无需更改Printable协议和Printer结构.
struct Generic<T> { var args: T func display() { print(Printer.print(args)) } }
选择打印的正确重载是在编译时确定的,而不是运行时.这是让人最困惑的事情.他们希望将Swift视为JavaScript,其中一切都是动态的. Swift喜欢静态,因为它可以确保你的类型是正确的,它可以做很多优化(Swift喜欢做编译器优化).那么,编译时,args是什么类型的?嗯,这是T. T是否知道是可打印的?不它不是.所以它使用非Printable版本.
但是当Swift使用PrintableObj专门使用Generic时,它是否知道它是可打印的?编译器无法在此时创建不同版本的显示器吗?是的,如果我们在编译时知道这个函数将存在的每个调用者,并且它们都不会被扩展为Printable(这可能发生在一个完全不同的模块中).如果不创建许多奇怪的边角情况(例如内部事物的行为与公共事物不同),并且不强制Swift主动生成某些未来调用者可能需要的每个可能的显示版本,就很难解决这个问题.斯威夫特可能会及时改善,但我认为这是一个难题. (Swift已经遭受了一些性能降低,因此公共泛型可以在不访问原始源代码的情况下进行专业化.这会使问题变得更加复杂.)
好的,我们明白了. T不可打印.但是如果我们在编译时知道一个明确无法打印的类型并且在这个函数中生存了呢?那会有用吗?
func display() { if let p = args as? Printable { print(Printer.print(p)) } else { print(Printer.print(args)) } }
哦,这么近……但不完全.这几乎可行. if-let实际上完全符合您的要求. p被分配.这是可打印的.但它仍然调用非Printable函数. ?!?!?!?!
这个地方我个人认为斯威夫特目前正处于破产状态,并希望能够解决这个问题.它甚至可能是一个错误.问题是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类型的橡皮擦中.)
当然,正确的答案是不要在打印机中以这种方式使用泛型重载.这太令人困惑和脆弱了.它看起来很漂亮,但它一直在爆炸.