【问题标题】:How to cast generic number type 'T' to CGFloat如何将通用数字类型“T”转换为 CGFloat
【发布时间】:2017-01-22 00:27:34
【问题描述】:

我有一个通过泛型 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)。

【问题讨论】:

  • 相关:How can I convert between related types through a common initializer?。您可以通过在您的协议要求中添加“影子方法”(例如_asOther() -&gt; T)来静态解决您的问题,从而允许符合协议的每种类型将自己转换为另一种类型。
  • 你能根据我的例子展示一下实现吗?如果它更干净/更短等,我很乐意接受这个答案。
  • 我不确定您是否会认为它更简洁或更短,但是按照我建议的方式进行操作有好处,我在下面的回答中对此进行了解释:)跨度>

标签: swift generics type-conversion


【解决方案1】:

作为to my answer here 的扩展,您可以通过使用“影子方法”以允许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 在 Swift 3 中,CGFloat 已经有一个 public init(_ value: CGFloat)。你在使用 Swift 2 吗?
【解决方案2】:

解决方案在我的掌握之中:)首先看这个链接:

Richard Fox - Cast-Free Arithmetic in Swift

然后它就像在我现有的代码中添加以下内容一样简单:

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()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-18
    • 2019-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多