【问题标题】:How to properly support Int values in CGFloat math in Swift?如何在 Swift 中正确支持 CGFloat 数学中的 Int 值?
【发布时间】:2015-01-26 05:35:02
【问题描述】:

目标

我(像网络上的许多其他人一样)希望在 CGFloat 数学中使用 Int 变量和文字,因为到目前为止可读性和易于开发超过了可能的精度损失。当您在整个应用程序中使用手动布局而不是使用情节提要时,这一点最为明显。

因此,以下内容无需任何手动 CGFloat 强制转换即可工作:

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)

方法

由于我们无法为 Swift 类型添加隐式转换,我必须添加适当的运算符,它们采用 CGFloatInt

func / (a: CGFloat, b: Int) -> CGFloat { return a / CGFloat(b) }
func / (a: Int, b: CGFloat) -> CGFloat { return CGFloat(a) / b }

问题

当 Swift 试图从整数文字隐式创建 CGFloat 值时,这两个运算符变得模棱两可。它不知道要转换两个操作数中的哪一个(例如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 /

问题

有没有什么方法可以用没有歧义且所有测试用例都成功的方式来声明操作符?

【问题讨论】:

  • 我建议不要创建这些运算符并明确地向你想要投射的方向进行投射......为了将来维护你认为这个运算符会改进的任何代码。
  • @nhgrif 是什么原因?
  • @nhgrif 当您彻底命名变量时,代码完全可读,无需显式转换。当公式被强制转换污染时,它的可读性会降低。 .height = (100 * views.count) + 100 完全没问题,.height = (100 * CGFloat(views.count)) + 100 完全没必要。
  • 无论你在那里做什么,你都应该使用自动布局...
  • @nhgrif 如果我想快速开发并保持应用程序性能,请不要这样做。无论如何,这两个讨论都是题外话。 Swift 应该是可扩展的,但这种能力在这里被破坏了——很可能是由于一个错误。

标签: swift casting implicit-conversion implicit-typing


【解决方案1】:

对于初学者,tl;dr: NO


问题在于我们要求编译器隐含地做太多事情。

我将使用常规函数来回答这个问题,因为我想明确一点,这与运算符无关。


所以,我们需要以下4个函数:

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
}

现在我们处于相同的场景中。我将忽略所有有效的方法并专注于这两种情况:

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

在第一个场景中,我们要求 Swift 隐式地​​确定一件事:bar1 应该是什么类型?我们传递给foo 的两个参数是1,它的类型是IntegerLiteralConvertible,和2,它又是IntegerLiteralConvertible 类型。

因为foo 只有一个覆盖,它接受两个Int 参数,所以Swift 能够确定bar1 应该是什么类型,这就是foo(Int,Int) 覆盖返回的任何类型,即@987654333 @。

现在,考虑我们添加以下函数的场景:

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

现在,场景 1 变得模棱两可:

let bar1 = foo(1,2)

我们要求 Swift 在这里隐含地确定两件事:

  1. 使用哪个 foo 覆盖
  2. bar1 使用什么类型

满足该场景的方法不止一种。 bar1Int,我们使用foo(Int,Int)->Int 覆盖,或者bar2Float,我们使用foo(Int,Int)->Float 覆盖。编译器无法决定。

尽管如此,我们可以使情况不那么模棱两可:

let bar1: Int = foo(1,2)

在这种情况下,编译器知道我们需要foo(Int,Int)->Int 覆盖——它是唯一满足该场景的。或者我们可以这样做:

let bar2: Float = foo(1,2)

在这种情况下,编译器知道我们需要 foo(Int,Int)->Float 覆盖——这也是满足该场景的唯一方法。


但让我们回顾一下我的场景 2,这正是您遇到的问题场景:

let bar2: Float = foo(1,2)

没有 foo(Int,Int)->Float 覆盖(忘记关于我们添加此覆盖的场景 1 的所有内容)。但是,IntegerLiteralConvertible 类型可以隐式转换为不同类型的数值数据类型(仅字面整数...不是整数变量)。因此,编译器将尝试找到一个 foo 覆盖,该覆盖接受 IntegerLiteralConvertible 可以转换为的参数并返回一个 Float,我们已将其明确标记为 bar2 的类型。

嗯,IntegerLiteralConvertible 可以转换为 Float,因此编译器会找到三个函数,它们采用正确参数的某种组合:

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

而且编译器不知道使用哪个。怎么可能?为什么它应该优先将一个或另一个字面整数转换为浮点数?

所以我们得到了歧义问题。

我们可以给编译器另一个覆盖。这与我们在场景 2 中给出的相同:foo(Int,Int) -> Float,现在编译器可以执行以下操作:

let bar2: Float = foo(1,2)

因为在这种情况下,没有隐式转换 IntegerLiteralConvertibles,编译器能够找到匹配的函数:foo(Int, Int) -> Float


所以现在你在想:

好吧,让我添加这个foo(Int,Int)->Float,一切都会好起来的!

对吗?

好吧,很抱歉让您失望了,但考虑到以下两个功能:

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

我们仍然会遇到歧义问题:

let bar3 = foo(1,2)

foo 有两个覆盖,它们采用 Int(或 IntegerLiteralConvertible),因为我们没有指定 bar3 的类型,所以我们要求编译器找出适当的覆盖并隐式确定bar3 的类型,这是它无法做到的。

【讨论】:

  • 感谢您的详细解答。这就是我所期望的,尽管我仍然希望有人知道这种语言限制的解决方法。如果不是,我会在几天内接受答案。
  • 这甚至不是 Swift 特有的问题。对于任何允许重载函数和隐式类型变量的语言来说,这都是一个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-04-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多