3 ms·
To summarize, the author has two problems: 1. Bit shift is not part of the IntegerType protocol, when it should be (although the author could avoid the issue b
by gilgoomesh 12y ago
To summarize, the author has two problems:
1. Bit shift is not part of the IntegerType protocol, when it should be (although the author could avoid the issue by accumulating the bytes in a UIntMax instead of the generic type).
2. Construction from (and conversion to) a UIntMax bit pattern is not part of the IntegerType protocol, when it should be (done correctly, this addresses the author's sign and construction complaints)
The author incorrectly claims/implies that these are problems with generics or protocols or the diversity of integer types in Swift. They're really a problem of omissions in the standard library protocols that are forcing some very cumbersome workarounds. The necessary functionality exists, it just isn't part of the relevant protocols. Submit these as bugs to Apple.
Edit:
As a follow up, here's a version that gets around the standard library limitations using an unsafe pointer...
func integerWithBytes<T: IntegerType>(bytes:[UInt8]) -> T? {
if (bytes.count < sizeof(T)) {
return nil
}
var i:UIntMax = 0
for (var j = 0; j < sizeof(T); j++) {
i = i | (UIntMax(bytes[j]) << UIntMax(j * 8))
}
return withUnsafePointer(&i) { ip -> T in
return UnsafePointer<T>(ip).memory
}
}
Of course, at that point, why not simply reinterpret the array buffer directly...
func integerWithBytes<T: IntegerType>(bytes:[UInt8]) -> T? {
if (bytes.count < sizeof(T)) {
return nil
}
return bytes.withUnsafeBufferPointer() { bp -> T in
return UnsafePointer<T>(bp.baseAddress).memory
}
}
- st3fan 12y agoThis is a great pragmatic example. Swift is a fantastic language with many features that let you write safe and strict code. But do not forget, Swift sits on top of a foundation of C and Objective-C. In my opinion a great Swift programmer knows when to take advantage of that. And, if written correctly, like the last example above, it is totally possible to write elegant and safe code. In my opinion there is nothing wrong with an approach that uses something like withUnsafeBufferPointer().
- barrkel 12y agoThe more protocols that are added, the more concepts there are to scare people away from what they think of as relatively simple primitives. 26 is already a scary number of concepts to tie to simple whole numbers. To be clear, the complexity is inherent in using numbers programmatically. The only real way around it would be to reduce flexibility around overloading operators, forcing people to implement bundles of related operators that are all associated with a concept (protocol). This would decrease the utility of overloading for implementing some algebras.
- eridius 12y agoI definitely agree that, by far, the best way to do this is to just use type punning with pointers. But if you really want to rebuild this yourself, you can still do so without the unsafe pointer, by handling signed and unsigned separately. Unsigned is easy: func integerWithBytes<T: UnsignedIntegerType>(bytes: [UInt8]) -> T? { if bytes.count < sizeof(T.self) { return nil } var acc: UIntMax = 0 for var i = 0; i < sizeof(T.self); i++ { acc |= bytes[i].toUIntMax() << UIntMax(i * 8) } // UnsignedIntegerType defines init(_: UIntMax) return T(acc) } Signed is trickier because of the sign bit. You have to sign-extend manually: func integerWithBytes<T: SignedIntegerType>(bytes: [UInt8]) -> T? { if bytes.count < sizeof(T.self) { return nil } var acc: UIntMax = 0 for var i = 0; i < sizeof(T.self); i++ { acc |= bytes[i].toUIntMax() << UIntMax(i * 8) } if sizeof(T.self) < sizeof(UIntMax.self) { // sign-extend the accumulator first if bytes[sizeof(T.self)-1] & 0x80 != 0 { for var i = sizeof(T.self); i < sizeof(UIntMax.self); i++ { acc |= 0xFF << UIntMax(i * 8) } } } // We're assuming that IntMax is the same size as UIntMax and therefore // uses init(bitPattern:) to convert, but that should be safe. let signed = IntMax(bitPattern: acc) // SignedIntegerType defines init(_: IntMax) return T(signed) }
- BlackFingolfin 12y ago> Of course, at that point, why not simply reinterpret the array buffer directly... ... because then the behavior of the code depends on the endianess of the CPU, and thus is a potential future portability gotcha... ? :)
- zamalek 12y ago> not part of the IntegerType protocol, when it should be > Submit these as bugs to Apple. Language designers keep making this mistake. .Net has this problem. Supposedly a bug was submitted for it and it's not possible without breaking some backwards compatibility. Luckily Swift is beta (right?). Either way, how I solve this "safely" in .Net: 1. Never do bit operations against signed integers. The behavior for this varies wildly across languages, it best to just avoid this altogether. 2. A UInt64 is bit-identical to the Int64 that it was cast from. I'm guessing at the Swift syntax, but those concepts translate to: func integerWithBytes(bytes:[UInt8]) -> UInt64? { if (bytes.count < sizeof(T)) { return nil } for (var j = 0; j < sizeof(T); j++) { i = i | (bytes[j] << (j * 8)) // error! } return i } That's right: you simply don't need a generic method.