简体   繁体   中英

How to properly support Int values in CGFloat math in Swift?

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:

func foo(a: Int, b: Int) -> Int {
    return 1
}

func foo(a: Int, b: Float) -> Float {
    return 2.0
}

func foo(a: Float, b: Int) -> Float {
    return 3.0
}

func foo(a: Float, b: Float) -> Float {
    return 4.0
}

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:

let bar1 = foo(1,2)
let bar2: Float = foo(1,2)

In the first scenario, we're asking Swift to implicitly determine just one thing: What type should bar1 be? The two arguments we pass to foo are 1 , which is of type IntegerLiteralConvertible , and 2 , which is again of the type IntegerLiteralConvertible .

Because there is only one override for foo which takes two Int arguments, Swift is able to figure out what type bar1 should be, and that's whatever type the foo(Int,Int) override returns, which is Int .

Now, consider the scenario in which we add the following function:

func foo(a: Int, b: Int) -> Float {
    return 5.0
}

Now, scenario 1 becomes ambiguous:

let bar1 = foo(1,2)

We're asking Swift to determine TWO things implicitly here:

  1. Which override of foo to use
  2. What type to use for bar1

There is more than one way to satisfy the scenario. Either bar1 is an Int and we use the foo(Int,Int)->Int override, or bar2 is a Float , and we use the foo(Int,Int)->Float override. The compiler can't decide.

We can make the situation less ambiguous though as such:

let bar1: Int = foo(1,2)

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:

let bar2: Float = foo(1,2)

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:

let bar2: Float = foo(1,2)

There's not a foo(Int,Int)->Float override (forget everything about scenario 1 in which we added this override). However, the type IntegerLiteralConvertible can be implicitly cast to different types of numeric data types (only literal integers... not integer variables). So the compiler will try to find a foo override that takes arguments that IntegerLiteralConvertible can be cast to and returns a Float , which we've explicitly marked as bar2 's type.

Well, IntegerLiteralConvertible can be cast as a Float , 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:

let bar2: Float = foo(1,2)

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:

All right, let me just add this foo(Int,Int)->Float and everything will work!

Right?

Well, sorry to disappoint, but given the following two functions:

foo(Int,Int) -> Int
foo(Int,Int) -> Float

We will still run into ambiguity problems:

let bar3 = foo(1,2)

There are two overrides for foo that take an Int (or IntegerLiteralConvertible ), and because we haven't specified bar3 's type and we're asking the compiler both figure out the appropriate override and implicitly determine bar3 's type, which it cannot do.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM