2016-07-15 1 views
1

J'essaie d'écrire une fonction d'assistance qui va convertir un tableau d'index binaires en une classe conforme à OptionSet.Conversion d'un tableau d'index binaires en OptionSet

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

Cela ne peut pas compiler:

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

J'ai aussi essayé d'utiliser rawvalue:

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

Cela ne fonctionne pas aussi bien:

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

peut cela doit être fait? Ai-je besoin d'ajouter des contraintes supplémentaires sur T?

Je sais qu'il est possible de réécrire cette fonction pour utiliser un type concret, mais je veux le garder générique si possible.

Répondre

3

Le problème dans votre code est que Int64 et T.RawValue sont sans rapport avec et peuvent être différents types.

Mais chaque type entier non signé peut être converti à partir et à UIntMax, de sorte que le problème peut être résolu en limitant RawValue-UnsignedInteger.

En utilisant @ l'idée de OOPer pour définir un initialiseur personnalisé serait:

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)) 
    } 
} 

qui peut également être écrit comme

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

Tous les types de jeu d'options que je l'ai vu jusqu'à présent ont un non signé entier tapez comme valeur brute, mais notez que la même chose fonctionnerait également avec SignedInteger et IntMax.

Exemple:

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

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

Comparez aussi How do you enumerate OptionSetType in Swift? pour la tâche inverse.

+0

Merci, c'est très utile. Cela vaut-il la peine de tout diffuser sur UIntMax pour les plateformes 32 bits ou est-ce un problème? laisse le résultat: UIntMax = bitIndexes.reduce (UIntMax (0)) {UIntMax ($ 0) | UIntMax (1) << UIntMax ($ 1)} – Zmey

+1

@Zmey: Le type OptionSet peut avoir une RawValue 64 bits même sur des plates-formes 32 bits. Si la valeur intermédiaire est de 32 bits seulement, vous perdez des bits. Mais vous n'avez pas besoin de 'UIntMax ($ 0)' ou 'UIntMax (1)', ces types sont déduits automatiquement. - En fait, j'ai fait ce mal entre les deux, comme vous pouvez le voir dans l'historique des modifications. Cela devrait être correct maintenant. –

+1

@Zmey: Le résultat/accumulateur intermédiaire peut également avoir le type RawValue. Le problème est que l'opérateur '' 'de gauche '' n'est pas défini dans le protocole (Un) SignedInteger, donc vous devrez définir des extensions de protocole supplémentaires pour tous les types bruts possibles, ce que j'ai essayé d'éviter. - Dans la révision 1 de cette réponse, j'ai résolu cela par multiplication au lieu de gauche. Donc c'est possible, mais j'ai trouvé ce code plus élégant. –

3

Vous devrez peut-être un peu plus configurations pour rendre votre getOptionSet pour travailler:

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

Avec ceux ci-dessus, vous pouvez écrire votre getOptionSet comme ceci:

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) 
} 

Utilisation:

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) 

Ou, vous pouvez définir un initialiseur comme ceci:

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

que vous pouvez utiliser comme:

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

Merci, cela fonctionne très bien! L'addition de @ martin-r est également très utile pour convertir du tableau de Int-s. – Zmey

+0

Malheureusement, je ne peux pas accepter les deux réponses, je suis celui de Martin parce qu'il ne nécessite pas la définition de protocoles supplémentaires, je suis allé avec elle dans l'application. Mais j'apprécie beaucoup votre aide! – Zmey