如何将泛型数字类型'T'转换为CGFloat

5
我有一个类,通过泛型T类型接受数字类型,我想将其转换为CGFloat,但会抛出以下错误:
“无法使用类型为'T'的参数列表调用类型为'CGFloat'的初始化程序”
为了能够成功转换,我该在我的类中做什么呢?
“CGFloat(self.top)” - 这就是它不喜欢的地方
'T'类型定义如下:
protocol Numeric:Comparable, Equatable {
    //
    init(_ v:Float)
    init(_ v:Double)
    init(_ v:Int)
    init(_ v:UInt)
    init(_ v:Int8)
    init(_ v:UInt8)
    init(_ v:Int16)
    init(_ v:UInt16)
    init(_ v:Int32)
    init(_ v:UInt32)
    init(_ v:Int64)
    init(_ v:UInt64)
}
extension Float: Numeric {}
extension Double: Numeric {}
extension Int: Numeric {}
extension Int8: Numeric {}
extension Int16: Numeric {}
extension Int32: Numeric {}
extension Int64: Numeric {}
extension UInt: Numeric {}
extension UInt8: Numeric {}
extension UInt16: Numeric {}
extension UInt32: Numeric {}
extension UInt64: Numeric {}

class MyClass<T: Numeric> {
//...
    var top:T
}

当我使用as时,就会出现这个运行时错误

无法将类型为 'Swift.Double' (0x1002b64f8) 的值转换为 'CoreGraphics.CGFloat' (0x1004e8740)。


相关:如何通过公共初始化程序在相关类型之间进行转换?。您可以通过向协议要求添加“影子方法”来静态解决问题,例如 _asOther() -> T,允许符合协议的每种类型将自己转换为另一种类型。 - Hamish
你可以根据我的示例展示实现吗?如果它更简洁/更短等,我会很高兴接受这个答案。 - Lukasz 'Severiaan' Grela
我不确定你是否认为这样做更简洁或更短,但按照我建议的方式进行有好处,我已在下面的答案中解释了 :) - Hamish
2个回答

4

作为对我这里的回答的扩展,您可以通过使用“阴影方法”来静态地实现这一点,以允许Numeric类型将自己强制转换为任何其他Numeric类型(假设目标类型的初始化程序被列为协议要求)。

例如,您可以像这样定义您的Numeric协议:

protocol Numeric : Comparable, Equatable {

    init(_ v:Float)
    init(_ v:Double)
    init(_ v:Int)
    init(_ v:UInt)
    init(_ v:Int8)
    init(_ v:UInt8)
    init(_ v:Int16)
    init(_ v:UInt16)
    init(_ v:Int32)
    init(_ v:UInt32)
    init(_ v:Int64)
    init(_ v:UInt64)
    init(_ v:CGFloat)

    // 'shadow method' that allows instances of Numeric
    // to coerce themselves to another Numeric type
    func _asOther<T:Numeric>() -> T
}

extension Numeric {

    // Default implementation of init(fromNumeric:) simply gets the inputted value
    // to coerce itself to the same type as the initialiser is called on
    // (the generic parameter T in _asOther() is inferred to be the same type as self)
    init<T:Numeric>(fromNumeric numeric: T) { self = numeric._asOther() }
}

然后将类型转换为Numeric,像这样:

// Implementations of _asOther() – they simply call the given initialisers listed
// in the protocol requirement (it's required for them to be repeated like this,
// as the compiler won't know which initialiser you're referring to otherwise)
extension Float   : Numeric {func _asOther<T:Numeric>() -> T { return T(self) }}
extension Double  : Numeric {func _asOther<T:Numeric>() -> T { return T(self) }}
extension CGFloat : Numeric {func _asOther<T:Numeric>() -> T { return T(self) }}
extension Int     : Numeric {func _asOther<T:Numeric>() -> T { return T(self) }}
extension Int8    : Numeric {func _asOther<T:Numeric>() -> T { return T(self) }}
extension Int16   : Numeric {func _asOther<T:Numeric>() -> T { return T(self) }}
extension Int32   : Numeric {func _asOther<T:Numeric>() -> T { return T(self) }}
extension Int64   : Numeric {func _asOther<T:Numeric>() -> T { return T(self) }}
extension UInt    : Numeric {func _asOther<T:Numeric>() -> T { return T(self) }}
extension UInt8   : Numeric {func _asOther<T:Numeric>() -> T { return T(self) }}
extension UInt16  : Numeric {func _asOther<T:Numeric>() -> T { return T(self) }}
extension UInt32  : Numeric {func _asOther<T:Numeric>() -> T { return T(self) }}
extension UInt64  : Numeric {func _asOther<T:Numeric>() -> T { return T(self) }}

示例用法:

class MyClass<T : Numeric> {

    var top : T

    init(_ top:T) {
        self.top = top
    }

    func topAsCGFloat() -> CGFloat {
        return CGFloat(fromNumeric: top)
    }
}

let m = MyClass(Int32(42))
let converted = m.topAsCGFloat()

print(type(of:converted), converted) // prints: CGFloat 42.0

这个解决方案可能和实现一个通过每种符合Numeric的类型进行切换的方法一样长 - 然而,由于这个解决方案不依赖于运行时类型转换,编译器很可能有更多的优化机会。
它还从静态类型检查中受益,这意味着您不能将新类型与Numeric相一致,而不实现将该类型转换为另一种类型的Numeric的逻辑(在您的情况下,如果该类型未在switch中处理,则会在运行时崩溃)。
此外,由于封装性,它更具灵活性,因为将类型转换逻辑放在符合Numeric的每个具体类型中执行,而不是在处理可能情况的单个方法中执行。

我刚测试了一下,发现CGFloat会抛出一个错误,称它不符合Numeric协议,为此你需要:extension CGFloat{ public init(_ value: CGFloat){ self = value } } - Lukasz 'Severiaan' Grela
@Lukasz'Severiaan'Grela 在Swift 3中,CGFloat已经有了public init(_ value: CGFloat)。您是否在使用Swift 2? - Hamish

2

解决方案就在我的手中 :) 首先看看这个链接:

Richard Fox - 无类型转换算术运算符Swift 2

然后,只需将以下内容添加到我的现有代码中:

protocol Numeric:Comparable, Equatable {
    //
    init(_ v:Float)
    init(_ v:Double)
    init(_ v:Int)
    init(_ v:UInt)
    init(_ v:Int8)
    init(_ v:UInt8)
    init(_ v:Int16)
    init(_ v:UInt16)
    init(_ v:Int32)
    init(_ v:UInt32)
    init(_ v:Int64)
    init(_ v:UInt64)
    init(_ value: CGFloat)
}
extension Numeric {

    func convert<T: Numeric>() -> T {
        switch self {
        case let x as CGFloat:
            return T(x) //T.init(x)
        case let x as Float:
            return T(x)
        case let x as Double:
            return T(x)
        case let x as Int:
            return T(x)
        case let x as UInt:
            return T(x)
        case let x as Int8:
            return T(x)
        case let x as UInt8:
            return T(x)
        case let x as Int16:
            return T(x)
        case let x as UInt16:
            return T(x)
        case let x as Int32:
            return T(x)
        case let x as UInt32:
            return T(x)
        case let x as Int64:
            return T(x)
        case let x as UInt64:
            return T(x)
        default:
            assert(false, "Numeric convert cast failed!")
            return T(0)
        }
    }
}

extension CGFloat{
    public  init(_ value: CGFloat){
        self = value
    }
}

然后按以下方式使用它:let c:CGFloat = self.top.convert()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接