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

Проблема в коде, что Int64 и T.RawValue являются не связаны и могут быть разных типов.

Но каждый тип без знака целого числа может быть преобразован из и в UIntMax, так что проблема может быть решена путем ограничения RawValue к UnsignedInteger.

Использование @ идеи 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)) 
    } 
} 

Все типы параметров набора, которые я видел до сих пор есть беззнаковое целое число типа как необработанное значение, но обратите внимание, что то же самое будет также работать с SignedInteger и IntMax.

Пример:

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

Спасибо, это очень полезно. Стоит ли отличать все от UIntMax для 32-битных платформ или это не проблема? let result: UIntMax = bitIndexes.reduce (UIntMax (0)) {UIntMax ($ 0) | UIntMax (1) << UIntMax ($ 1)} – Zmey

+1

@Zmey: Тип OptionSet может иметь 64-битный RawValue даже на 32-битных платформах. Если промежуточное значение составляет 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

К сожалению, я не могу принять оба ответа, я отмечаю один из них Мартина, потому что он не требует определения дополнительных протоколов, я пошел с ним в приложении. Но я очень ценю вашу помощь! – Zmey