【问题标题】:Making Swift generics play with overloaded functions让 Swift 泛型与重载函数一起玩
【发布时间】:2016-05-22 01:24:57
【问题描述】:

我正在尝试构建一个泛型类型MyStruct<T>,它可以使用FloatDouble 进行内部存储。在初始化程序中,我传递了一个 T 类型的参数(我打算将其设为 FloatDouble)。该初始化程序调用一些三角函数,例如sin()cos()。这两个函数都在系统库中重载以提供FloatDouble 版本。

var f:Float=1.2
var d:Double=1.2

sin(f) //0.9320391
sin(d) //0.9320390859672263

问题是,我不能在我的通用结构中使用它们。精简后的案例如下所示:

struct MyStruct<T> {
    var v:T

    init(x:T){v=sin(x)}
}

因为:

Playground 执行失败:Untitled Page.xcplaygroundpage:9:17: 错误:无法使用“(T)”类型的参数列表调用“sin” 初始化(x:T){v=sin(x)}

Untitled Page.xcplaygroundpage:9:17:注意:“sin”的重载存在这些部分匹配的参数列表: (浮动),(双) 初始化(x:T){v=sin(x)}

似乎应该有办法让这项工作发挥作用,但感觉很像this situation。 cmets 建议没有办法要求存在全局函数。

我可以通过使用以下结构来强制这种情况:

init(x:T){v=T(sin(Double(x)))}

并对 T 施加约束,使其可以从 Double 构造,但这似乎违背了制作结构的 Float 版本的目的,即在将其用于代码的关键循环。

如果在库中将sin() 定义为泛型函数而不是重载函数,感觉会更容易:

func sin<T:FloatingPointType> (x:T) -> T

但事实就是如此。

有没有办法让我在标准库之上构建一个与 Float/Double 无关的库,而不会增加很多开销?

【问题讨论】:

  • “我有没有办法做到这一点而不会增加很多开销?”我不确定“this”应该是什么,但sin 是几个 C 函数,它们对 Swift 泛型(所以你不能使用占位符类型作为它的参数)或 Swift 协议(所以你可以'不要使用 FloatingPointType 作为其参数)。
  • @matt,我所说的“这个”是指在标准库之上创建一个与 Float/Double 无关的库。这似乎是对泛型的明显使用,但我只能做最基本的事情。

标签: swift generics


【解决方案1】:

不幸的是,没有像sin() 目前在 Swift 中实现的那样简单的方法。必须将其视为运算符(就像 Equatable== 的工作方式一样)才能让您将其添加为协议要求。

@matt's solution 是一个不错的快速修复,但如果您想要更永久的东西,您可能需要考虑创建一个协议,然后扩展浮点类型,以便允许您使用泛型重载 sin() 函数版本。

protocol FloatingPointMathType : FloatingPointType {
    var _sinValue : Self { get }
}

extension Float : FloatingPointMathType {
    var _sinValue : Float {return sin(self)}
}

extension Double : FloatingPointMathType {
    var _sinValue : Double {return sin(self)}
}

extension CGFloat : FloatingPointMathType {
    var _sinValue : CGFloat {return sin(self)}
}

func sin<T:FloatingPointMathType>(x:T) -> T {return x._sinValue}

(随意添加更多数学函数)

我们不得不在这里使用“影子”计算属性来弥补我们不能简单地将sin() 用作协议要求的事实。这并不理想 - 但可能与您将获得的一样好。

您现在可以继续使用sin() 作为通用函数:

struct MyStruct<T:FloatingPointMathType> {
    var v : T

    init(x:T) {
        v =  sin(x)
    }
}

print(MyStruct(x: Float(3.0)).v) // 0.14112
print(MyStruct(x: Double(3.0)).v) // 0.141120008059867
print(MyStruct(x: CGFloat(3.0)).v) // 0.141120008059867

【讨论】:

  • 这似乎没有运行时开销。我认为 Swift 可能会内联大多数额外的函数调用。我选择了func sin() -&gt; Float {return Foundation.sin(self)} 而不是 var,但我认为它是 6 合 1,6 合 1。我不确定为什么您不需要添加 Foundation 命名空间来避免引用循环,可能是因为您的泛型函数只是另一个重载?保持函数调用语法的通用函数很不错。
  • @Omegaman 乐于助人 :) 我可以从非泛型 sin 函数调用中省略 Foundation. 命名空间的原因是 Swift 总是更喜欢使用更具体的参数类型调用函数.因此,当您传入非泛型类型时,它将更喜欢非泛型 sin 函数而不是泛型函数,因此没有问题 - 尽管显然将其放入并没有什么害处。至于是否使用计算属性或功能,我怀疑它在优化方面没有任何区别,只是你喜欢使用的任何东西。
【解决方案2】:

可能会尝试这样的事情:

struct MyStruct<T:FloatingPointType> {
    var v:T
    init(x:T){
        switch x {
        case is Float:
            v = sin(x as! Float) as! T
        case is Double:
            v = sin(x as! Double) as! T
        case is CGFloat:
            v = sin(x as! CGFloat) as! T
        default:v = 0.0 as! T
        }
    }
}

似乎有效:

let s = MyStruct(x:Float(3))
s.v // 0.14112
let s2 = MyStruct(x:Double(3))
s2.v // 0.1411200080598672
let s3 = MyStruct(x:CGFloat(3))
s3.v // 0.1411200080598672

【讨论】:

  • 您可以使用case let x as Float 等使这个稍微好一点。这样您就可以消除第一组强制向下转换。
  • 这在紧要关头有效,我担心它可能是唯一的选择,直到@originaluser2 找到了一种方法,通过一些巧妙的协议专业化和通用函数来隐藏混乱。这里的主要缺点是运行时类型检查的开销。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-22
  • 1970-01-01
  • 1970-01-01
  • 2013-09-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多