Search code examples
swiftgenericspowdowncasttype-constraints

Swift's pow() function won't accept Doubles as arguments


I created this infix operator ^^ as a substitute to using the pow function:

infix operator ^^ { associativity left precedence 155 }
func ^^ <T: IntegerLiteralConvertible>(left: T, right: T) -> T {
    return pow(left as Double, right as Double)
}

I used the IntegerLiteralConvertible protocol as a type constraint for the generics left and right, because from my understanding this diagramm shows, that it basically includes all number types.

In order to use the pow function I have to downcast left and right to Double though, which I did using the as operator. It's not the safest approach, but that's besides the point.

When implementing the function this way swift tells me:

<stdin>:4:12: error: cannot invoke 'pow' with an argument list of type '(Double, Double)'
return pow(left as Double, right as Double)
       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Now as far as I know pow takes two Doubles as parameters, so why is it complaining about this?


Solution

  • why is it complaining about this?

    Because pow returns Double. And Double is not identical to T. The error message is misleading, but it means "Cannot find pow that accepts (Double, Double) and returns T type"


    I think you are misunderstanding "cast" in Swift. In Swift as does not convert numeric types.

    let intVal:Int = 12
    let doubleVal:Double = intVal as Double
    //                            ^ [!] error: 'Int' is not convertible to 'Double'
    

    And If the type of operand is not predictable at compile time, invalid casting causes runtime error:

    func foo<T: IntegerLiteralConvertible>(x: T)  {
        x as Double // <-- Execution was interrupted
    }
    foo(12 as Int)
    

    Instead, we must explicitly "convert" them. see the document: Numeric Type Conversion

    let intVal:Int = 12
    let doubleVal:Double = Double(intVal)
    

    This works only because Double has init(_ v: Int) initializer. The following code does not compile:

    func foo<T: IntegerLiteralConvertible>(x: T)  {
        Double(x)
    //  ^~~~~~~~~ [!] error: cannot invoke 'init' with an argument of type 'T'
    }
    

    Because Double does not have init<T:IntegerLiteralConvertible>(_ val:T) initializer.

    So, if you want to use pow(), you must convert T to Double for arguments, and convert Double to T for returning value. And there is no simple solution for that.