我试图建立一系列可以相互转换的类型。例如,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
的约束。
答案 0 :(得分:3)
问题在于,在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))
}
}
潜在的解决方案是通过switch
x
可能的具体类型在运行时解决此问题。然而,这并不像静态解决这个问题那样好,因为您可以从提高安全性和在某些情况下提高性能中受益。
为了静态地执行此操作,您可以添加通用_asOther
&#39;阴影&#39;对您的协议起作用,可以将给定的浮点类型转换为另一个,并将具体类型的初始化添加到您的协议要求中。
这样您就不必列出所有可能的转化组合 - 您现在只需从初始化程序中调用_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
)会更有意义 - 否则你只会不必要地失去精确度。