The problem is that in your init(_ x:FloatConvertible)
, Swift cannot infer what the concrete type of x
is. It just knows that it's a FloatConvertible
. Therefore when you try to do Self(x)
, while it can infer the concrete type of Self
, it doesn't know which initialiser you want to call, meaning that it will default to your init(_ x:FloatConvertible)
initialiser, thus creating an infinite loop.
If you give your custom initialiser an argument name, you'll see that Swift complains that it can't find the correct initialiser:
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))
}
}
A potential solution therefore is to resolve this at runtime by switch
ing over the concrete types that x
could be. However this isn't nearly as good as resolving this statically, as you can benefit from increased safety and in some cases increased performance.
In order to do this statically, you could add a generic _asOther
'shadow' function to your protocol that can convert a given floating point type to another, as well as adding the concrete type's initialisers to your protocol requirement.
This will save you from having to list out all the possible combinations of conversions – you can now just invoke _asOther
from your initialiser.
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
In the initialiser, _asOther
will be called on the input value, with the type of self
being inferred for the generic parameter T
(in this context self
is guaranteed to be a concrete type). The _asOther
function will then get called on x
, which will return the value as the given destination type.
Note that you don't have to use the fromOther:
argument label for your custom initialiser – this will still work without any label. Although I would strongly advocate for using it to catch any problems with your code at compile time (Swift would accept code that would cause infinite loops at runtime otherwise).
Also as a side note, you should maybe re-think your design for how you want your *
overload to work. It would make more sense to be returning the more precise type that you input into it (i.e Float * Double = Double
) – otherwise you're just needlessly losing precision.