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
bar1
be? The two arguments we pass tofoo
are1
, which is of typeIntegerLiteralConvertible
, and2
, which is again of the typeIntegerLiteralConvertible
.Because there is only one override for
foo
which takes twoInt
arguments, Swift is able to figure out what typebar1
should 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:
foo
to usebar1
There is more than one way to satisfy the scenario. Either
bar1
is anInt
and we use thefoo(Int,Int)->Int
override, orbar2
is aFloat
, and we use thefoo(Int,Int)->Float
override. 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)->Int
override--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)->Float
override--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)->Float
override (forget everything about scenario 1 in which we added this override). However, the typeIntegerLiteralConvertible
can be implicitly cast to different types of numeric data types (only literal integers... not integer variables). So the compiler will try to find afoo
override that takes arguments thatIntegerLiteralConvertible
can be cast to and returns aFloat
, which we've explicitly marked asbar2
's type.Well,
IntegerLiteralConvertible
can be cast as aFloat
, so the compiler finds three functions that take some combination of the right argument:foo(Int,Float) -> Float
foo(Float,Int) -> Float
foo(Float,Float) -> Float
And 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
foo
that 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.