Omegaman - 1 year ago 61
Swift Question

# How can I convert between related types through a common initializer?

I'm trying to build up a family of types that can be converted to each other. For example, Float and Double can be converted to each other through their initializers. I'd like to not have to create an exhaustive list of initializers showing that each type can convert to every other type.

I tried to do something like this in a Playground, but it crashes:

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

My eventual goal isn't just to do the conversion, but to multiply
`a`
by
`b`
like so:

``````func *<T:FloatConvertible, U:FloatConvertible> (a:T, b:U) -> T{
return a * T(b)
}
``````

So that I can express the multiply.

Is there a way to do this? I think part of the problem is winding up with a structure that looks like
`Double(Double(Double(Double(...)))`
, but I don't think I can put a constraint that ensures
`T != U`
.

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.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download