2016-07-15 86 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