Omegaman
Omegaman

Reputation: 2329

Making Swift generics play with overloaded functions

I'm trying to build a generic type MyStruct<T> that can use either Float or Double for internal storage. In the initializer, I'm passing an argument of type T (which I intend to be either Float or Double). That initializer calls some trig functions such as sin() and cos(). Both of those functions are overloaded in the system libraries to provide Float and Double versions.

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

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

Trouble is, I can't use them in my generic struct. A stripped down case would look like this:

struct MyStruct<T> {
    var v:T

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

Because:

Playground execution failed: Untitled Page.xcplaygroundpage:9:17: error: cannot invoke 'sin' with an argument list of type '(T)' init(x:T){v=sin(x)}

Untitled Page.xcplaygroundpage:9:17: note: overloads for 'sin' exist with these partially matching parameter lists: (Float), (Double) init(x:T){v=sin(x)}

It seems like there should be a way to make this work, but it feels a lot like this situation. Where the comments suggest there is no way to require the existence of a global function.

I can force the situation by using a construct such as:

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

and putting a constraint on T that it can be constructed from a Double, but that seems to defeat the purpose of making the Float version of the struct, which is to reduce computation effort when this is used in a critical loop of code.

It feels like this would have been easier if sin() had been defined in the library as a generic function rather than an overloaded function:

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

but it is what it is.

Is there a way for me to build a library that is Float/Double agnostic on top of the standard libraries without adding a lot of overhead?

Upvotes: 4

Views: 3816

Answers (2)

Hamish
Hamish

Reputation: 80801

Unfortunately, there's no easy way to do this the way sin() is currently implemented in Swift. It would have to be treated as an operator (like how Equatable and == works) in order to allow you to add it as a protocol requirement.

@matt's solution is a nice quick fix, but if you want something more permanent, you may want to consider creating a protocol and then extending the floating point types in order to allow you to overload the sin() function with a generic version.

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}

(Feel free to add more math functions)

We're having to use a 'shadow' calculated property here to make up for the fact that we can't simply use sin() as a protocol requirement. It's not ideal – but probably about as good as you're going to get.

You can now go ahead and use sin() as a generic function:

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

Upvotes: 5

matt
matt

Reputation: 535138

Might try something like this:

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
        }
    }
}

Seems to work:

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

Upvotes: 0

Related Questions