2016-07-15 74 views
1

我想寫一個幫助函數,它將位索引數組轉換爲符合OptionSet的類。將位索引數組轉換爲OptionSet

func getOptionSet<T: OptionSet>(bitIndexes: [Int64]) -> T { 
    var result: Int64 = 0 
    for index in bitIndexes { 
     result |= 1 << index 
    } 
    return T(rawValue: result) // error 
} 

這無法編譯:

Cannot invoke initializer for type 'T' with an argument list of type '(rawValue: Int64)' 

我也嘗試使用RawValue:

func getOptionSet<T: OptionSet>(bitIndexes: [T.RawValue]) { 
    var result = T.RawValue() // error 

這並不工作,以及:

Cannot invoke value of type 'T.RawValue.Type' with argument list '()' 

能這樣做?我是否需要在T上增加額外的約束條件?

我知道有可能重寫這個函數來使用具體的類型,但是我想盡可能保持它的通用性。

回答

3

的問題在你的代碼是Int64T.RawValue是 無關的,可以是不同類型。

但每一個的無符號整數類型可以從被轉換和 到UIntMax,所以問題可以通過限制RawValueUnsignedInteger來解決。

使用@ OOPer的想法,來定義初始化,這將是:

extension OptionSet where RawValue: UnsignedInteger { 
    init(bitIndexes: [Int]) { 
     var result: UIntMax = 0 
     for index in bitIndexes { 
      result |= 1 << UIntMax(index) 
     } 
     self.init(rawValue: RawValue(result)) 
    } 
} 

這也可以寫成

extension OptionSet where RawValue: UnsignedInteger { 
    init(bitIndexes: [Int]) { 
     let result = bitIndexes.reduce(UIntMax(0)) { 
      $0 | 1 << UIntMax($1) 
     } 
     self.init(rawValue: RawValue(result)) 
    } 
} 

我所見過的所有選項設置類型到目前爲止有一個無符號整數 類型爲原始值,但請注意,同樣可以使用 SignedIntegerIntMax

實施例:

struct TestSet: OptionSet { 
    let rawValue: UInt16 
    init(rawValue: UInt16) { 
     self.rawValue = rawValue 
    } 
} 

let ts = TestSet(bitIndexes: [1, 4]) 
print(ts) // TestSet(rawValue: 18) 

也比較How do you enumerate OptionSetType in Swift?用於反向任務。

+0

謝謝,這非常有用。是否值得將所有內容轉換爲32位平臺的UIntMax還是不是問題?讓結果:UIntMax = bitIndexes.reduce(UIntMax(0)){UIntMax($ 0)| UIntMax(1)<< UIntMax($ 1)} – Zmey

+1

@Zmey:即使在32位平臺上,OptionSet類型也可以具有64位的RawValue。如果中間值僅爲32位,則會丟失位。但你不需要'UIntMax($ 0)'或'UIntMax(1)',這些類型是自動推斷的。 - 其實我從中可以看出編輯歷史中的錯誤。現在應該是正確的。 –

+1

@Zmey:中間結果/累加器也可以具有RawValue類型。問題是左移運算符<< <<沒有在(Un)SignedInteger協議中定義,所以你必須爲所有可能的原始類型定義額外的協議擴展,我試圖避免這種擴展。 - 在這個答案的修訂1中,我通過乘法而不是左移解決了這個問題。所以這是可能的,但我發現這個代碼更優雅。 –

3

您可能需要多一點的設置,讓您getOptionSet工作:

protocol OptionBitShiftable: IntegerLiteralConvertible { 
    func << (lhs: Self, rhs: Self) -> Self 
    func |= (lhs: inout Self, rhs: Self) 
} 
extension Int64: OptionBitShiftable {} 
extension UInt64: OptionBitShiftable {} 
//... 

有了上面這些,你可以寫你getOptionSet這樣的:

func getOptionSet<T: OptionSet where T.RawValue: OptionBitShiftable>(bitIndexes: [T.RawValue]) -> T { 
    var result: T.RawValue = 0 
    for index in bitIndexes { 
     result |= 1 << index 
    } 
    return T(rawValue: result) 
} 

用法:

struct MyOptionSet: OptionSet { 
    var rawValue: Int64 
    init(rawValue: Int64) { 
     self.rawValue = rawValue 
    } 
} 
let myOption: MyOptionSet = getOptionSet(bitIndexes: [1,2,3,5,7]) 
print(myOption.rawValue) //->174(=2+4+8+32+128) 

或者,您可以像這樣定義一個初始化程序:

extension OptionSet where RawValue: OptionBitShiftable { 
    init(bitIndexes: [RawValue]) { 
     var result: RawValue = 0 
     for index in bitIndexes { 
      result |= 1 << index 
     } 
     self.init(rawValue: result) 
    } 
} 

您可以使用如:

let alsoMyOption = MyOptionSet(bitIndexes: [4, 6]) 
print(alsoMyOption.rawValue) //->80(=16+64) 
+0

謝謝,這個作品很棒! @ martin-r加法對於從Int-s數組轉換也非常有用。 – Zmey

+0

可悲的是,我不能接受這兩個答案,因爲它不需要定義附加協議,所以我將其標記爲Martin的一個答案,我在應用程序中使用了它。但我非常感謝你的幫助! – Zmey