I first came up with this idea while reading the responses to the C bit fields thread. But the idea got pushed back and I may have forgotten some details.
I was thinking of re-proposing after first simplifying the bit-field idea with just fixed-sized arrays of Bool
, then I thought about property wrappers again. I read the guide (Swift 5.1 back then, and again for 5.2) and wondered if we could use them here.
@propertyWrapper
struct Compacted<Value: [_! ; Bool], Storage: FixedWidthInteger> {
@constexpr private static let wholeWordCount = Value.count / Storage.bitWidth
@constexpr private static let partialWordCount = Value.count % Storage.bitWidth == 0 ? 0 : 1
@constexpr private static let wordCount = wholeWordCount + partialWordCount
private typealias Words = [wordCount ; Storage]
private storage: Words
public var wrappedValue: Value {
get {
// All of this could be done better.
var result: Value
for r inout result {
let (wordIndex, bitIndex) = #indexOf(r)[0].quotientAndRemainder(dividingBy: Storage.bitWidth)
r = storage[wordIndex] & (1 << bitIndex) != 0
}
return result
}
set {
self = Self(wrappedValue: newValue, into: Storage.self)
}
}
@inlinable public var projectedValue: Self { return self }
public init(into type: Storage.Type) {
storage = fill(a: Words.self, with: 0)
}
public init(wrappedValue: Value, into type: Storage.Type) {
self.init(into: type)
for w inout storage {
let start = #indexOf(w)[0] * Storage.bitWidth
for vi in start..<Swift.min(start + Storage.bitWidth, Value.count) {
if wrappedValue[vi] {
word |= 1 << (vi - start)
}
}
w = word
}
}
}
// I don't know if this would be legal for defaulting the storage type.
extension Compacted where Storage == UInt {
@inlinable public init(wrappedValue: Value) {
self.init(wrappedValue: wrappedValue, into: Storage.self)
}
@inlinable public init() {
self.init(into: Storage.self)
}
}
extension Compacted: RandomAccessCollection, MutableCollection {
@inlinable public startIndex: Int { return 0 }
@inlinable public endIndex: Int { return Value.count }
public subscript(position: Int) -> Bool {
get {
let (wordIndex, bitIndex) = position.quotientAndRemainder(dividingBy: Storage.bitWidth)
let mask: Storage = 1 << bitIndex
return storage[wordIndex] & mask != 0
}
set {
let (wordIndex, bitIndex) = position.quotientAndRemainder(dividingBy: Storage.bitWidth)
let mask: Storage = 1 << bitIndex
if newValue {
storage[wordIndex] |= mask
} else {
storage[wordIndex] &= ~mask
}
}
}
}
// To-Do: Add Equatable/Hashable/etc. support.
where [_! ; Bool]
is the protocol-analogue for one-dimensional fixed-size arrays of Bool
where the length must be determined by compile-time, and you should be able to guess any other new example features. This ends up simulating C++'s std::bitset
. Property wrappers are more amazing than I first thought after reading they've been added to Swift.