Goal
I (like many others on the web) would like to use Int variables and literals in CGFloat math since readability & ease of development outweigh a possible loss in precision by far. This is most noticeable when you use manual layout throughout an app instead of using the Storyboard.
So the following should work without any manual CGFloat casts:
let a = CGFloat(1)
let b = Int(2)
let c = a / b // Cannot invoke / with an arguments list of type (CGFloat, Int)
let d = b / a // Cannot invoke / with an arguments list of type (Int, CGFloat)
let e = a / 2 // => CGFloat(0.5)
let f = 2 / a // => CGFloat(2.0)
let g = 2 / b // => Int(1)
let h = b / 2 // => Int(1)
let i = 2 / 2 // => Int(1)
let j: CGFloat = a / b // Cannot invoke / with an arguments list of type (CGFloat, Int)
let k: CGFloat = b / a // Cannot invoke / with an arguments list of type (Int, CGFloat)
let l: CGFloat = a / 2 // => CGFloat(0.5)
let m: CGFloat = 2 / a // => CGFloat(2.0)
let n: CGFloat = 2 / b // Cannot invoke / with an arguments list of type (IntegerLiteralConvertible, Int)
let o: CGFloat = b / 2 // Cannot invoke / with an arguments list of type (Int, IntegerLiteralConvertible)
let p: CGFloat = 2 / 2 // => CGFloat(1.0)
Approach
Since we cannot add implicit conversions to Swift types I had to add appropriate operators which take CGFloat and Int.
func / (a: CGFloat, b: Int) -> CGFloat { return a / CGFloat(b) }
func / (a: Int, b: CGFloat) -> CGFloat { return CGFloat(a) / b }
Problem
The two operators become ambiguous when Swift tries to implicitly create CGFloat values from integer literals. It doesn't know which of the two operands to convert (example case p).
let a = CGFloat(1)
let b = Int(2)
let c = a / b // => CGFloat(0.5)
let d = b / a // => CGFloat(2.0)
let e = a / 2 // => CGFloat(0.5)
let f = 2 / a // => CGFloat(2.0)
let g = 2 / b // => Int(1)
let h = b / 2 // => Int(1)
let i = 2 / 2 // => Int(1)
let j: CGFloat = a / b // => CGFloat(0.5)
let k: CGFloat = b / a // => CGFloat(2.0)
let l: CGFloat = a / 2 // => CGFloat(0.5)
let m: CGFloat = 2 / a // => CGFloat(2.0)
let n: CGFloat = 2 / b // => CGFloat(1.0)
let o: CGFloat = b / 2 // => CGFloat(1.0)
let p: CGFloat = 2 / 2 // Ambiguous use of operator /
Question
Is there any way to declare the operators in a way where there is no ambiguous use and all test cases succeed?
For starters, tl;dr: NO.
The problem is that we're asking the compiler to do too much implicitly.
I'm going to use regular functions for this answer, because I want it to be clear that this has nothing to do with operators.
So, we need the following 4 functions:
And now we're in the same scenario. I'm going to ignore all of the ones that work and focus on these two scenarios:
In the first scenario, we're asking Swift to implicitly determine just one thing: What type should
bar1be? The two arguments we pass tofooare1, which is of typeIntegerLiteralConvertible, and2, which is again of the typeIntegerLiteralConvertible.Because there is only one override for
foowhich takes twoIntarguments, Swift is able to figure out what typebar1should be, and that's whatever type thefoo(Int,Int)override returns, which isInt.Now, consider the scenario in which we add the following function:
Now, scenario 1 becomes ambiguous:
We're asking Swift to determine TWO things implicitly here:
footo usebar1There is more than one way to satisfy the scenario. Either
bar1is anIntand we use thefoo(Int,Int)->Intoverride, orbar2is aFloat, and we use thefoo(Int,Int)->Floatoverride. The compiler can't decide.We can make the situation less ambiguous though as such:
In which case the compiler knows we want the
foo(Int,Int)->Intoverride--it's the only one that satisfies the scenario. Or we can do:In which case the compiler knows we want the
foo(Int,Int)->Floatoverride--again, the only way to satisfy the scenario.But let's take a look back at my scenario 2, which is exactly the problem scenario you have:
There's not a
foo(Int,Int)->Floatoverride (forget everything about scenario 1 in which we added this override). However, the typeIntegerLiteralConvertiblecan be implicitly cast to different types of numeric data types (only literal integers... not integer variables). So the compiler will try to find afoooverride that takes arguments thatIntegerLiteralConvertiblecan be cast to and returns aFloat, which we've explicitly marked asbar2's type.Well,
IntegerLiteralConvertiblecan be cast as aFloat, so the compiler finds three functions that take some combination of the right argument:foo(Int,Float) -> Floatfoo(Float,Int) -> Floatfoo(Float,Float) -> FloatAnd the compiler doesn't know which to use. How can it? Why should it prioritize casting one or the other of your literals integers to a float?
So we get the ambiguity problem.
We can give the compiler another override. It's the same one we gave it in scenario 2:
foo(Int,Int) -> Float, and now the compiler is okay with the following:Because in this case, without implicitly casting the
IntegerLiteralConvertibles, the compiler was able to find a function that matched:foo(Int, Int) -> Float.So now you're thinking:
Right?
Well, sorry to disappoint, but given the following two functions:
We will still run into ambiguity problems:
There are two overrides for
foothat take anInt(orIntegerLiteralConvertible), and because we haven't specifiedbar3's type and we're asking the compiler both figure out the appropriate override and implicitly determinebar3's type, which it cannot do.