一尘不染

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

swift

我正在尝试建立一个可以相互转换的类型家族。例如,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


阅读 256

收藏
2020-07-07

共1个答案

一尘不染

问题是,在您的中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在协议中添加一个通用的“影子”函数,该函数可以将给定的浮点类型转换为另一种浮点类型,也可以将具体类型的初始化程序添加到协议要求中。

这将使您不必列出所有可能的转换组合-您现在可以_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,它将返回该值作为给定的目标类型。

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


另外请注意,您可能应该重新设计,以了解*过载的工作方式。返回您输入的更精确的类型(即Float * Double = Double)会更有意义-
否则,您不必要地失去了精度。

2020-07-07