Based on https://developer.apple.com/documentation/swift/optionset , I notice that it is not possible to have more than 64 members in OptionSet
For instance
struct Options: OptionSet {
let rawValue: Int
static let options0 = Options(rawValue: 1 << 0)
static let options1 = Options(rawValue: 1 << 1)
static let options2 = Options(rawValue: 1 << 2)
static let options3 = Options(rawValue: 1 << 3)
// ...
// ...
// ...
static let options63 = Options(rawValue: 1 << 63)
// rawValue = 0
static let options64 = Options(rawValue: 1 << 64)
// rawValue = 0
static let options65 = Options(rawValue: 1 << 65)
}
let options: Options = [.options64]
precondition(!options.contains(.options65))
For the above code example, the set shouldn't contain option65 as it is not inserted initially.
However, since both 1 << 64
and 1 << 65
ended up as 0, this will yield wrong result.
I was wondering, is this the limitation of OptionSet
of not able to have more than 64 members? Or, there is a workaround?
CodePudding user response:
An OptionSet
needs to implement the methods
mutating func formUnion(_ other: Self)
mutating func formIntersection(_ other: Self)
mutating func formSymmetricDifference(_ other:Self)
from the SetAlgebra
protocol, and the operator
static func == (lhs: Self, rhs: Self) -> Bool
from the Equatable
protocol.
If the rawValue
is a type that conforms to the FixedWidthInteger
protocol then the standard library provides default implementations for all these requirements. So one option (as suggested by Sweeper) is to use an integer type with more than 64 bits.
But actually not the full power of FixedWidthInteger
is needed for OptionSet
, only the bitwise AND, OR, and XOR operations. So here is another possible solution:
First define a protocol for the required bitwise operations:
protocol BitwiseOperators {
static func | (lhs: Self, rhs: Self) -> Self
static func & (lhs: Self, rhs: Self) -> Self
static func ^ (lhs: Self, rhs: Self) -> Self
static var allZeros: Self { get }
}
(Remark: There used to be a BitwiseOperation
protocol in Swift 3, but that does not exist anymore.)
Next we define default implementations for all required OptionSet
methods if the raw value conforms to the BitwiseOperators
type:
extension OptionSet where RawValue: BitwiseOperators & Equatable {
init() {
self.init(rawValue: RawValue.allZeros)
}
mutating func formUnion(_ other: Self) {
self = Self(rawValue: self.rawValue | other.rawValue)
}
mutating func formIntersection(_ other: Self) {
self = Self(rawValue: self.rawValue & other.rawValue)
}
mutating func formSymmetricDifference(_ other:Self) {
self = Self(rawValue: self.rawValue ^ other.rawValue)
}
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.rawValue == rhs.rawValue
}
}
Now, in order to define an option set with more than 64 possible options, we need a raw value type which can store the necessary number of bits, and implements the AND, OR, and XOR operations. Here is an example for 128 bits, but it can easily be extended to any necessary size:
struct Raw128: Equatable, BitwiseOperators {
let lo: UInt64
let hi: UInt64
static func | (lhs: Raw128, rhs: Raw128) -> Raw128 {
Raw128(lo: lhs.lo | rhs.lo, hi: lhs.hi | rhs.hi)
}
static func & (lhs: Raw128, rhs: Raw128) -> Raw128 {
Raw128(lo: lhs.lo & rhs.lo, hi: lhs.hi & rhs.hi)
}
static func ^ (lhs: Raw128, rhs: Raw128) -> Raw128 {
Raw128(lo: lhs.lo ^ rhs.lo, hi: lhs.hi ^ rhs.hi)
}
static var allZeros: Raw128 { Raw128(lo: 0, hi: 0) }
}
As a convenience we can define an initializer which sets exactly one bit at a given position:
extension Raw128 {
init(bitPos: Int) {
switch bitPos {
case 0..<64: lo = 1 << bitPos; hi = 0
case 64..<128: lo = 0; hi = 1 << (bitPos - 64)
default: fatalError("`bit` must be in the range 0...127")
}
}
}
Finally we can define the Options
options set with Raw128
as the raw value type:
struct Options: OptionSet {
let rawValue: Raw128
init(rawValue: Raw128) {
self.rawValue = rawValue
}
static let options0 = Options(rawValue: RawValue(bitPos: 0))
static let options1 = Options(rawValue: RawValue(bitPos: 1))
static let options2 = Options(rawValue: RawValue(bitPos: 2))
// ...
static let options63 = Options(rawValue: RawValue(bitPos: 63))
static let options64 = Options(rawValue: RawValue(bitPos: 64))
static let options65 = Options(rawValue: RawValue(bitPos: 65))
}
and everything works as expected:
let options: Options = [.options64]
print(!options.contains(.options65)) // true
CodePudding user response:
Although Swift only provides fixed width integer types for up to 64 bits, OptionSet
only requires that RawValue
to be any kind of FixedWidthInteger
for the default SetAlgebra
implementations to be synthesised.
When creating an option set, include a
rawValue
property in your type declaration. For your type to automatically receive default implementations for set-related operations, therawValue
property must be of a type that conforms to theFixedWidthInteger
protocol, such asInt
orUInt8
.
Therefore, you can technically always create your FixedWidthInteger
of arbitrary size, by combining smaller integers, and use it in an option set.
For example, here is a UInt128
implementation I found. I can use it in an option set just like normal:
struct Foo: OptionSet {
let rawValue: UInt128
static let option1 = Foo(rawValue: 1 << 0)
static let option2 = Foo(rawValue: 1 << 1)
static let option3 = Foo(rawValue: 1 << 2)
// ...
static let option65 = Foo(rawValue: 1 << 64)
}
print([Foo.option1, .option65] as Foo) // Foo(rawValue: 18446744073709551617)
Though obviously, this is not going to be as fast as the natively supported integers, and admittedly, if someone hasn't already implemented the size you want, implementing a whole FixedWidthInteger
just for an OptionSet
isn't very practical.