Zmey Zmey - 4 months ago 21
Swift Question

Converting array of bit indexes to OptionSet

I'm trying to write a helper function which will convert an array of bit indexes to a class conforming to OptionSet.

func getOptionSet<T: OptionSet>(bitIndexes: [Int64]) -> T {
var result: Int64 = 0
for index in bitIndexes {
result |= 1 << index
}
return T(rawValue: result) // error
}


This fails to compile:

Cannot invoke initializer for type 'T' with an argument list of type '(rawValue: Int64)'


I've also tried using RawValue:

func getOptionSet<T: OptionSet>(bitIndexes: [T.RawValue]) {
var result = T.RawValue() // error


This doesn't work as well:

Cannot invoke value of type 'T.RawValue.Type' with argument list '()'


Can this be done? Do I need to add additional constraints on T?

I know it's possible to rewrite this function to use a concrete type, but I want to keep it generic if possible.

Answer

The problem in your code is that Int64 and T.RawValue are unrelated and can be different types.

But every unsigned integer type can be converted from and to UIntMax, so the problem can be solved by restricting RawValue to UnsignedInteger.

Using @OOPer's idea to define a custom initializer this would be:

extension OptionSet where RawValue: UnsignedInteger {
    init(bitIndexes: [Int]) {
        var result: UIntMax = 0
        for index in bitIndexes {
            result |= 1 << UIntMax(index)
        }
        self.init(rawValue: RawValue(result))
    }
}

which can also be written as

extension OptionSet where RawValue: UnsignedInteger {
    init(bitIndexes: [Int]) {
        let result = bitIndexes.reduce(UIntMax(0)) {
            $0 | 1 << UIntMax($1)
        }
        self.init(rawValue: RawValue(result))
    }
}

All option set types that I have seen so far have an unsigned integer type as raw value, but note that the same would also work with SignedInteger and IntMax.

Example:

struct TestSet: OptionSet {
    let rawValue: UInt16
    init(rawValue: UInt16) {
        self.rawValue = rawValue
    }
}

let ts = TestSet(bitIndexes: [1, 4])
print(ts) // TestSet(rawValue: 18)

Compare also How do you enumerate OptionSetType in Swift 2? for the reverse task.