我正在尝试建立一个可以相互转换的类型家族。例如,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像这样:
a
b
func *<T:FloatConvertible, U:FloatConvertible> (a:T, b:U) -> T{ return a * T(b) }
这样我就可以表示乘法。
有没有办法做到这一点?我认为问题的一部分是由看起来像这样的结构来解决的Double(Double(Double(Double(...))),但是我认为我不能施加确保的约束T != U。
Double(Double(Double(Double(...)))
T != U
问题是,在您的中init(_ x:FloatConvertible),Swift无法推断具体的类型x。它只是知道这是一个FloatConvertible。因此,当您尝试做时Self(x),虽然它 可以 推断的具体类型Self,但它不知道您要调用哪个初始化程序,这意味着它将默认为您的init(_ x:FloatConvertible)初始化程序,从而造成无限循环。
init(_ x:FloatConvertible)
x
FloatConvertible
Self(x)
Self
如果为自定义初始化程序指定一个参数名称,则会看到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可以。但是,这不像静态解决方案那样好,因为您可以从提高的安全性以及某些情况下提高的性能中受益。
switch
为了静态地执行此操作,您可以_asOther在协议中添加一个通用的“影子”函数,该函数可以将给定的浮点类型转换为另一种浮点类型,也可以将具体类型的初始化程序添加到协议要求中。
_asOther
这将使您不必列出所有可能的转换组合-您现在可以_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然后,该函数将在on上调用x,它将返回该值作为给定的目标类型。
self
T
请注意,您 不必fromOther:在自定义初始化程序中使用参数标签- 在没有任何标签的情况下仍然可以使用。尽管我强烈建议在编译时使用它来捕获代码中的任何问题(否则Swift会接受在运行时会导致无限循环的代码)。
fromOther:
另外请注意,您可能应该重新设计,以了解*过载的工作方式。返回您输入的更精确的类型(即Float * Double = Double)会更有意义- 否则,您不必要地失去了精度。
*
Float * Double = Double