I have a class that accepts numeric types through generic T type, I wanted to be able to convert it to the CGFloat
but it throws:
Cannot invoke initializer for type 'CGFloat' with an argument list of type '(T)'
What I have to do in my class to be able to convert it successfully?
CGFloat(self.top)
- this is what it didnt like
The 'T' type is defined as follows:
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
}
When I tried with as
then this runtime error popped up
Could not cast value of type 'Swift.Double' (0x1002b64f8) to 'CoreGraphics.CGFloat' (0x1004e8740).
As an extension to my answer here, you could achieve this statically through using a 'shadow method' in order to allow Numeric
types to coerce themselves to any other Numeric
type (given that the initialiser for the destination type is listed as a protocol requirement).
For example, you could define your Numeric
protocol like so:
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() }
}
And then conform types to Numeric
like so:
// 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) }}
Example usage:
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
This solution is probably no shorter than implementing a method that switches through every type that conforms to Numeric
– however as this solution doesn't rely on runtime type-casting, the compiler will likely have more opportunities for optimisation.
It also benefits from static type-checking, meaning that you cannot conform a new type to Numeric
without also implementing the logic to convert that type to another type of Numeric
(in your case, you would crash at runtime if the type wasn't handled in the switch
).
Furthermore, because of encapsulation, it's more flexible to expand, as the logic to convert types is done in each individual concrete type that conforms to Numeric
, rather than a single method that handles the possible cases.
Solution was within the grasp of my hand:) first look at this link:
Richard Fox - Cast-Free Arithmetic in Swift
Then it was as simple as adding following to my existing code:
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
}
}
And then use it as follows: let c:CGFloat = self.top.convert()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With