Convert an Array (of known fixed size) to a tuple

I see what you mean. Your solution might be simpler for your specific use case.

But that Float_x51 was just meant as an example of a type that can implement the FixedSizeArray protocol. In your scenario that would have been the Uniforms struct imported from C.

That is, in your project you have this:

// my-c-types.h

typedef struct {
    float weights[51];
} Uniforms;

And you have a bridging header file with #import "my-c-types.h".

Then, you'd use the FixedSizeArray protocol as follows (no matter if the C array has 51, 42 or 123 elements):

extension Uniforms : FixedSizeArray { // This is all you have to do to
    static var count: Int { 51 }      // add subscript access to a
    typealias Element = Float         // C imported type like Uniforms.
}

func test() {
    var u = Uniforms()
    for i in 0 ..< u.count {
        u[i] = Float(i) // Or whatever you'd like to set the values to.
    }
}
test()

And, as long as you use the regular subscript (rather than the unchecked one) it will trap if you accidentally specified a count or Element that doesn't match the memory layout of the C struct.

Since the protocol has subscripts and count, it can easily be extended to implement RandomAccessCollection.

Like this.
protocol FixedSizeArray : RandomAccessCollection {
    associatedtype Element
    static var count: Int { get }
}
extension FixedSizeArray {
    public var count: Int { return Self.count }
    public var startIndex: Int { 0 }
    public var endIndex: Int { Self.count }

    public subscript(unchecked index: Int) -> Element {
        get {
            return withUnsafeBytes(of: self) { (ptr) -> Element in
                let o = index &* MemoryLayout<Element>.stride
                return ptr.load(fromByteOffset: o, as: Element.self)
            }
        }
        set {
            withUnsafeMutableBytes(of: &self) { (ptr) -> Void in
                let o = index &* MemoryLayout<Element>.stride
                ptr.storeBytes(of: newValue, toByteOffset: o, as: Element.self)
            }
        }
    }
    public subscript(index: Int) -> Element {
        get {
            typealias ML<T> = MemoryLayout<T>
            precondition(ML<Self>.alignment == ML<Element>.alignment)
            precondition(Self.count * ML<Element>.stride == ML<Self>.stride)
            precondition(0 <= index && index < Self.count)
            return self[unchecked: index]
        }
        set {
            typealias ML<T> = MemoryLayout<T>
            precondition(ML<Self>.alignment == ML<Element>.alignment)
            precondition(Self.count * ML<Element>.stride == ML<Self>.stride)
            precondition(0 <= index && index < Self.count)
            self[unchecked: index] = newValue
        }
    }
}

If the Uniforms C struct had contained not only the array, you'd have to modify the protocol so that you conform by also specifying a byteOffset to where the array starts (as well as count and Element). But in such cases you'd probably want to use an all together different approach.

2 Likes