简体   繁体   中英

IntegerType: Cannot invoke 'init' with an argument of type 'T'

I have this function that returns an integer within bounds:

func randomNumber<T: IntegerType>(min: T, max: T) -> T {
    let n = max - min + 1
    let u = UInt32(n)            // Error: Cannot invoke 'init' with an argument of type 'T'
    let r = arc4random_uniform(u) 
    return r + min
}

I dont understand why this is not working because UInt32 top most protocol is UnsignedIntegerType , which conforms to IntegerType .

I have to make n into UInt32 because arc4random_uniform() takes a UInt32 as parameter

Why wont this work?

The trouble is that UInt32 has no init that takes an arbitrary IntegerType . Sure, it takes every defined one in the standard library, but what if someone implemented a UInt128 that conformed to IntegerType ? Even if you substituted in let u = UInt32(n.toIntMax()) , you'll be stuck when trying to add r to min since, again, there isn't an implementation of + that will add a UInt32 to any arbitrary IntegerType . This makes sense given the overflow possibilities – you know arc4random_uniform(u) will never return UInt32 greater than, say, an Int8.max , but Swift can't. You'd need much richer functionality than IntegerType provides to write a truly generic version of this function that had correct pre- and post-conditions.

You can create a new Integer Protocol to accept any UInt and also limit UInt64 to UInt32.max to conform to arc4random_uniform limit as follow:

protocol Integer  {
    init(_ value:Int)
    var integerValue: Int { get }
}

extension Int     : Integer { var integerValue : Int { return self      } }
extension UInt64  : Integer { var integerValue : Int { return Int(self) } }
extension UInt32  : Integer { var integerValue : Int { return Int(self) } }
extension UInt16  : Integer { var integerValue : Int { return Int(self) } }
extension UInt8   : Integer { var integerValue : Int { return Int(self) } }
extension UInt    : Integer { var integerValue : Int { return Int(self) } }

func randomNumber(min: Integer, max: Integer) -> Int {
    if min.integerValue >= max.integerValue             { return 0 }
    if max.integerValue-min.integerValue+1 > UInt32.max { return 0 }
    return (min.integerValue + arc4random_uniform(UInt32(max.integerValue - min.integerValue + 1))).integerValue
}

randomNumber(UInt(10), UInt64(13))
randomNumber(UInt8(10), UInt32(13))
randomNumber(UInt16(10), UInt16(13))
randomNumber(UInt32(10), UInt8(13))
randomNumber(UInt64(10), UInt(13))

You need to make at least 2 functions: one for SignedIntegerType , one for UnsignedIntegerType .

SignedIntegerType has type coercing functions: toIntMax() and init(_: IntMax)

protocol _SignedIntegerType : _IntegerType, SignedNumberType {

    /// Represent this number using Swift's widest native signed integer
    /// type.
    func toIntMax() -> IntMax

    /// Convert from Swift's widest signed integer type, trapping on
    /// overflow.
    init(_: IntMax)
}

UnsignedIntegerType also has type coercing functions: toUIntMax() and init(_: UIntMax)

protocol _UnsignedIntegerType : _IntegerType {

    /// Represent this number using Swift's widest native unsigned
    /// integer type.
    func toUIntMax() -> UIntMax

    /// Convert from Swift's widest unsigned integer type, trapping on
    /// overflow.
    init(_: UIntMax)
}

Using these functions, you can:

func randomNumber<T: UnsignedIntegerType>(min: T, max: T) -> T {
    let n = max - min + 1
    let u = UInt32(n.toUIntMax())
    let r = arc4random_uniform(u)
    return T(r.toUIntMax()) + min
}

func randomNumber<T: SignedIntegerType>(min: T, max: T) -> T {
    let n = max - min + 1
    let u = UInt32(n.toIntMax())
    let r = arc4random_uniform(u)
    return T(r.toIntMax()) + min
}

But, we already have handy numericCast builtin functions:

func numericCast<T : _UnsignedIntegerType, U : _SignedIntegerType>(x: T) -> U
func numericCast<T : _SignedIntegerType, U : _UnsignedIntegerType>(x: T) -> U
func numericCast<T : _UnsignedIntegerType, U : _UnsignedIntegerType>(x: T) -> U
func numericCast<T : _SignedIntegerType, U : _SignedIntegerType>(x: T) -> U

numericCast can simplify your implementations:

func randomNumber<T: UnsignedIntegerType>(min: T, max: T) -> T {
    return min + numericCast(arc4random_uniform(numericCast(max - min + 1)))
}

func randomNumber<T: SignedIntegerType>(min: T, max: T) -> T {
    return min + numericCast(arc4random_uniform(numericCast(max - min + 1)))
}

Inner numericCast converts T to UInt32 , outer one converts UInt32 to T .

Now, these functions have exact the same implementation codes :) But I think you cannot unify them into one function.

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