2017-07-29 8 views
2

Je veux écrire un moyen efficace d'écrire 0 et 1 dans un octet (ou tout autre type).Comment écrire un masque de bits en C++ 14 en utilisant (quelque chose comme?) Des modèles variadiques

Par exemple, en C, on peut écrire quelque chose comme:

uint8_t x = 0x00; 
x|= (1 << 2) | (1 << 4); 

à écrire 1 en bits 2 et 4. (bien sûr, ne pas utiliser 2 et 4, mais utiliser des macros pour rappeler la signification des bits 2 et 4).

Je n'aime pas ces approche, donc j'écris le modèle variadique suivant:

template<typename T> 
T bitmask(T p0) 
{ 
    return (1 << p0); 
} 

template<typename T, typename...Position> 
T bitmask(T p0, Position... p1_n) 
{ 
    return (1 << p0)|bit_mask(p1_n...); 
} 



template<typename T, typename... Position> 
T& write_one(T& x, Position... pos0_n) 
{ 
    x|= bit_mask(pos0_n...); 

    return x; 
} 

et ceux-ci fonctionnent. Vous pouvez écrire quelque chose comme ceci:

uint8_t x = 0x00; 
write_one(x, 2, 4); 

Mais je préfèrerais une autre solution. Je voudrais écrire quelque chose comme:

write_one<uint8_t>(x, 2, 4); // if x is uint8_t 
write_one<uint16_t>(x, 2, 4); // if x is uint16_t 

Le type de write_one est le type de x (ok, je sais que vous n'avez pas besoin d'écrire le type uint8_t et uint16_t, je l'ai écrit pour plus de clarté). Les autres paramètres sont toujours des nombres (en fait, ils sont uint8_t).

Comment puis-je les obtenir?

Je veux écrire un code comme le suivant:

template<typename T> 
T bitmask(uint8_t p0) 
{ 
    return (1 << p0); 
} 

template<typename T> 
T bitmask(T p0, uint8_t... p1_n) 
{ 
    return (1 << p0)|bit_mask<T>(p1_n...); 
} 

template<typename T> 
T& write_one(T& x, uint8_t... pos0_n) 
{ 
    x|= bit_mask<T>(pos0_n...); 

    return x; 
} 

Merci beaucoup.

+0

Avez-vous vérifié la sortie assembleur de ce que vous avez fait jusqu'à présent? vous serez surpris de la qualité du compilateur –

Répondre

3

Ces deux méthodes produisent exactement le même assembleur optimisé:

#include <utility> 

template <int...bits, class Int> 
constexpr auto set_bits_a(Int i) 
{ 
    using expand = int[]; 
    void(expand{ 
     0, 
     ((i |= (Int(1) << bits)),0)... 
    }); 
    return i; 
} 

template <class Int, class...Bits> 
constexpr auto set_bits_b(Int i, Bits...bits) 
{ 
    using expand = int[]; 
    void(expand{ 
     0, 
     ((i |= (Int(1) << bits)),0)... 
    }); 
    return i; 
} 

int get_value(); 
volatile int x, y; 

int main() 
{ 
    x = set_bits_a<1, 3, 5>(get_value()); 
    y = set_bits_b(get_value(), 1, 3, 5); 
} 

sortie:

main: 
     sub  rsp, 8 
     call get_value() 
     or  eax, 42     ; <-- completely optimised 
     mov  DWORD PTR x[rip], eax 
     call get_value() 
     or  eax, 42     ; <-- completely optimised 
     mov  DWORD PTR y[rip], eax 
     xor  eax, eax 
     add  rsp, 8 
     ret 
y: 
x: 

https://godbolt.org/g/CeNRVw

+0

Merci beaucoup. Mais je ne comprends pas la signification de: void (expand { 0, ((i | = (Int (1) << bits)), 0) ... }); Pourquoi un casting est-il nul? Et le ..., dans ce cas, qu'est-ce que c'est? – Antonio

+0

@Antonio le '...' s'appelle un elipsis. Sa fonction est d'étendre l'argument variadique. Je cast à void pour éviter les avertissements du compilateur sur les expressions inutilisées. L'utilisation de 'expand' est nécessaire jusqu'à C++ 17 quand nous aurons des opérateurs de pliage. –

+0

Merci, Richard. – Antonio