gpt4 book ai didi

swift - 如何通过通用初始化程序在相关类型之间进行转换?

转载 作者:可可西里 更新时间:2023-11-01 00:59:21 24 4
gpt4 key购买 nike

我正在尝试建立一个可以相互转换的类型族。例如,Float 和 Double 可以通过它们的初始化器相互转换。我不想创建一个详尽的初始化列表来表明每种类型都可以转换为其他类型。

我试图在 Playground 中做这样的事情,但它崩溃了:

protocol FloatConvertible {
init(_ x:FloatConvertible)
}

extension FloatConvertible {
init(_ x:FloatConvertible){self.init(Self(x))}
}

extension Float:FloatConvertible {}
extension Double:FloatConvertible {}

func transmute<T:FloatConvertible, U:FloatConvertible>
(a:T, b:U) -> T {
return T(b)
}

transmute(Float(3.1), b: Double(2.6))

我的最终目标不仅仅是进行转换,而是将 a 乘以 b,如下所示:

func *<T:FloatConvertible, U:FloatConvertible> (a:T, b:U) -> T{
return a * T(b)
}

这样我就可以表达乘法了。

有没有办法做到这一点?我认为部分问题在于一个看起来像 Double(Double(Double(Double(...))) 的结构,但我不认为我可以设置一个约束来确保T != U

最佳答案

问题是在你的 init(_ x:FloatConvertible) 中,Swift 无法推断出 x 的具体类型是什么。它只知道它是一个 FloatConvertible。因此,当您尝试执行 Self(x) 时,虽然它可以推断Self 的具体类型,但它不知道您使用哪个初始化器想要调用,这意味着它将默认为您的 init(_ x:FloatConvertible) 初始化程序,从而创建一个无限循环。

如果你给你的自定义初始化器一个参数名称,你会看到 Swift 提示它找不到正确的初始化器:

protocol FloatConvertible {
init(c x:FloatConvertible)
}

extension FloatConvertible {
init(c x:FloatConvertible) {
// error: missing argument name 'c:' in call
// (i.e it can't find the concrete type's initialiser)
self.init(Self(x))
}
}

因此,可能的 解决方案是在运行时通过切换x 可能是的具体类型来解决此问题。然而,这远不如静态解决这个问题好,因为您可以受益于更高的安全性,在某些情况下还可以提高性能。

为了静态地做到这一点,你可以添加一个通用的 _asOther 'shadow' 函数到你的协议(protocol)中,它可以将给定的浮点类型转换为另一个,以及将具体类型的初始化程序添加到您的协议(protocol)要求。

这将使您不必列出所有可能的转换组合 - 您现在只需从初始化程序中调用 _asOther

protocol FloatConvertible {
init(_ other:Float)
init(_ other:Double)
init(_ other:CGFloat)
init(fromOther x:FloatConvertible)

func _asOther<T:FloatConvertible>() -> T
}

extension FloatConvertible {
init(fromOther x:FloatConvertible) {self = x._asOther()}
}

// note that we have to implement these for each extension,
// so that Swift uses the concrete types of self, preventing an infinite loop
extension Float : FloatConvertible {
func _asOther<T:FloatConvertible>() -> T {return T(self)}
}

extension Double : FloatConvertible {
func _asOther<T:FloatConvertible>() -> T {return T(self)}
}

extension CGFloat : FloatConvertible {
func _asOther<T:FloatConvertible>() -> T {return T(self)}

// note that CGFloat doesn't implement its own initialiser for this,
// so we have to implement it ourselves
init(_ other:CGFloat) {self = other}
}

func transmute<T:FloatConvertible, U:FloatConvertible>(value: T, to: U.Type) -> U {
return U(fromOther: value)
}

let f = transmute(value: CGFloat(2.6), to: Float.self)
print(type(of: f), f) // prints: Double 2.59999990463257

在初始化器中,_asOther 将在输入值上被调用,self 的类型被推断为通用参数 T (在这种情况下,self 保证是具体类型)。 _asOther 函数随后将在 x 上被调用,它将返回作为给定目标类型的值。

请注意,您不必为您的自定义初始化程序使用fromOther: 参数标签——这在没有任何标签的情况下仍然有效。尽管我强烈建议使用它在编译时捕获代码的任何问题(Swift 会接受会在运行时导致无限循环的代码)。


另请注意,您可能应该重新考虑您希望 * 重载如何工作的设计。返回您输入的更精确的类型(即 Float * Double = Double)会更有意义 – 否则您只是不必要地失去精度。

关于swift - 如何通过通用初始化程序在相关类型之间进行转换?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37556276/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com