Sure, but the compound type doesn't have to be a tuple. e.g. here's a very rough sketch of a minimal implementation of Zip2Collection with an enum index (forgive the poorly chosen names and any errors):
struct Zip2Collection<S1: Collection, S2: Collection>: Collection {
private var s1: S1
private var s2: S2
typealias Element = (S1.Element, S2.Element)
enum Zip2CollectionIndex: Comparable {
case pair(S1.Index, S2.Index)
case end
static func < (lhs: Zip2CollectionIndex, rhs: Zip2CollectionIndex) -> Bool {
switch (lhs, rhs) {
case (.end, .end): return false
case (.end, _): return false
case (_, .end): return true
case let (.pair(lhsi1, lhsi2), .pair(rhsi1, rhsi2)):
guard (lhsi1 < rhsi1) == (lhsi2 < rhsi2) else { fatalError("index mismatch")}
return lhsi1 < rhsi1
}
}
}
typealias Index = Zip2CollectionIndex
var startIndex: Index { return .pair(s1.startIndex, s2.startIndex) }
var endIndex: Index { return .end }
subscript(index: Index) -> Element {
guard case let .pair(i1, i2) = index else { fatalError("invalid index") }
return (s1[i1], s2[i2])
}
func index(after i: Index) -> Index {
switch i {
case .end: fatalError("can't advance endIndex")
case let .pair(i1, i2):
let nexti1 = s1.index(after: i1)
let nexti2 = s2.index(after: i2)
if nexti1 == s1.endIndex || nexti2 == s2.endIndex {
return .end
}
return .pair(nexti1, nexti2)
}
}
init(_ s1: S1, _ s2: S2) {
self.s1 = s1
self.s2 = s2
}
}
A real implementation would want to hide the index type better, so invalid indices can't be directly constructed, and do a lot of specialisation for performance reasons.