2017-07-20 2 views
8

Je voudrais implémenter une fonction template qui génère des masques de bits lors de la compilation pour les types entiers. Ces masques doivent être basés sur des motifs de 8 bits où le motif sera répété consécutivement pour remplir l'entier. L'exemple suivant fait exactement ce que je veux, mais en temps d'exécution:Créer un masque de bits basé sur un modèle comme constexpr

#include <iostream> 
#include <type_traits> 
#include <cstring> 

template<typename Int> 
typename std::enable_if<std::is_integral<Int>::value, Int>::type 
make_mask(unsigned char pattern) { 
    Int output {}; 
    std::memset(&output, pattern, sizeof(Int)); 
    return output; 
} 

int main() { 
    auto mask = make_mask<unsigned long>(0xf0); 
    std::cout << "Bitmask: '" << std::hex << mask << "'" << std::endl; 
} 

La sortie du code ci-dessus est:

Bitmask: 'f0f0f0f0f0f0f0f0' 

Je sais que l'optimiseur peut éliminer l'ensemble de l'appel de fonction dans le code ci-dessus, mais je cherche une solution constexpr avec et éventuellement avec .

Répondre

12

Intuitivement, je ferais un répéteur octet:

template<class Int, int count, int byte> 
struct byte_repeater; 

template<class Int, int byte> 
struct byte_repeater<Int, 1, byte> { 
    static const Int value = byte; 
}; 

template<class Int, int count, int byte> 
struct byte_repeater { 
    static const Int value = (byte_repeater<Int, count-1, byte>::value << CHAR_BIT) | byte; 
}; 

Une interface facile à utiliser:

template<class Int, int mask> 
struct make_mask { 
    static const Int value = byte_repeater<Int, sizeof(Int), mask>::value; 
}; 

Et cela fonctionne dans 03 C++. Peut-être même plus vieux. Compilation Here.

Dans les versions plus récentes de C++, il existe probablement des moyens de simplifier ce processus. Heck, même en C++ 03, il peut probablement être simplifié.

+0

Votre exemple fonctionne également avec 'C++ 98'. Je pense que 'byte_repeater' n'est pas nécessaire, son paramètre' count' dans le template primaire peut être initialisé comme 'count = sizeof (Int)' alors le 'byte_repeater' peut être utilisé de la même manière que' make_mask' si le 'count 'est le dernier paramètre. Néanmoins, c'est un bon point de faire des modèles séparés pour cacher le paramètre 'count' qui n'est qu'un détail d'implémentation. – Akira

+1

Malgré les autres réponses ont été utiles aussi et difficile de choisir celui à accepter, j'accepte celui-ci en raison de la rétrocompatibilité. – Akira

4

Vous pouvez simplement l'écrire:

template<typename Int, typename = std::enable_if_t<std::is_integral<Int>::value>> 
constexpr Int make_mask(unsigned char pattern) { 
    constexpr auto numBytes = sizeof(Int); 
    Int result = 0; 

    for (std::size_t i = 0; i < numBytes; i++) { 
     result |= static_cast<Int>(pattern) << (i*8); 
    } 

    return result; 
} 

Demo

Cela ne fonctionne que pour les types non signés, mais vous pouvez le faire fonctionner pour les types signés en appelant la version non signée et jetant à le type signé:

template<typename Int, std::enable_if_t<std::is_integral<Int>::value && std::is_unsigned<Int>::value, int> = 0> 
constexpr Int make_mask(unsigned char pattern) { 
    constexpr auto numBytes = sizeof(Int); 
    Int result = 0; 

    for (std::size_t i = 0; i < numBytes; i++) { 
     result |= static_cast<Int>(pattern) << (i*8); 
    } 

    return result; 
} 

template<typename Int, std::enable_if_t<std::is_integral<Int>::value && std::is_signed<Int>::value, int> = 0> 
constexpr Int make_mask(unsigned char pattern) { 
    return static_cast<Int>(make_mask<std::make_unsigned_t<Int>>(pattern)); 
} 

Demo

+0

J'aime comment votre solution gère les types intégraux 'signed' et' unsigned'. Une solution très facile à comprendre pour 'C++ 14' et plus. Merci pour ça! – Akira

-3

avez-vous vraiment besoin de toutes ces complications? Pourquoi ne pas utiliser une macro pour ça?

#define PATTERN(A) 0x##A##A##A##A##A##A##A##A 

cout << hex << PATTERN(f0) << endl; 

travaillera en C++ 98 (et dans la plaine 'c' sans Cout) :-)

ou si vous voulez vraiment C11/++, cela fonctionne aussi:

constexpr long long int pattern(const long long unsigned pattern) { 
    return (pattern << 56) | (pattern << 48) | (pattern << 40) 
      | (pattern << 32) | (pattern << 24) | (pattern << 16) 
      | (pattern << 8) | pattern ; 
} 
+0

Ceci est une solution merdique mais cela fonctionne certainement. Je ne comprends pas pourquoi il y a des downvotes. –

+2

@HenriMenke Parce que ce n'est pas une solution; il ne le fait que pour l'un des types entiers. – Justin

+0

cela fonctionne pour le type demandé dans la question. BTW, c'est la solution la plus efficace de l'efficacité de la compilation. Eh bien, il a ses propres inconvénients, comme pas de portée, mais il est léger et simple. – Serge

2

Que dire de déclarer make_mask() comme constexpr, le modifier en ajoutant un paramètre par défaut, en utilisant shift-bit, bit-or et récursivité?

Je veux dire

#include <climits> 
#include <iostream> 

template <typename Int> 
constexpr typename std::enable_if<std::is_integral<Int>::value, Int>::type 
     make_mask (unsigned char pattern, std::size_t dim = sizeof(Int)) 
{ 
    return dim ? ((make_mask<Int>(pattern, dim-1U) << CHAR_BIT) | pattern) 
       : Int{}; 
} 

int main() 
{ 
    constexpr auto mask = make_mask<unsigned long>(0xf0); 
    std::cout << "Bitmask: '" << std::hex << mask << "'" << std::endl; 
} 

P.S .: fonctionne avec 11 C++ aussi.

+0

Merci pour votre réponse! J'aime la simplicité de ton exemple. – Akira

4

Je ne suis pas sûr qu'il existe une solution bien définie pour les types signés.Pour les types non signés, je partirais avec:

template<class Int> 
constexpr typename std::enable_if</* std::is_integral<Int>::value && */ std::is_unsigned<Int>::value, 
Int>::type make_mask(const unsigned char pattern) { 
    return ((std::numeric_limits<Int>::max()/std::numeric_limits<unsigned char>::max()) * pattern); 
} 

Cela devrait fonctionner à condition que std::numeric_limits<Int>::max() est un multiple de std::numeric_limits<unsigned char>::max(); vous pouvez ajouter une vérification à la condition std::enable_if et utiliser une autre solution si cette vérification échoue.

+1

Belle solution! Mais je suppose que si vous vérifiez pour 'std :: is_unsigned' vous pouvez éviter de vérifier' std :: is_integral'. Je veux dire: la partie SFINAE pourrait être simplifiée comme 'typename std :: enable_if :: value, Int> :: type' – max66

+0

Est-ce que' std :: is_unsigned :: value == true' implique que 'std :: is_integral :: value == true'? Je peux le croire, mais je n'ai pas vu cette garantie pendant ma brève recherche. –

+0

Selon ccpreference, 'std :: is_unsigned' est' true' seulement pour "les types entiers non signés et le type bool"; peut-être échouer pour le type 'bool'. – max66

1

C'est juste une multiplication. En outre, vous voulez vous assurer de ne pas courir dans les pièges:

  • défendre contre is_integral<bool> être vrai
  • cette fonction a un sens complètement différent dans une machine qui ne dispose pas d'un octet 8 bits , donc juste de compiler pour refuser ces machines
  • défendre contre débordements de signe, donc il suffit d'utiliser uintmax_t

Farce tous ces contrôles sur la signature de la fonction est illisible, donc je l'ai utilisé static_assert():

template <typename IntType> 
constexpr IntType 
make_mask (unsigned char pattern) 
{ 
    static_assert (CHAR_BIT == 8, ""); 
    static_assert (std::is_integral<IntType>::value, ""); 
    static_assert (not std::is_same<typename std::decay <IntType>::type, bool>::value, ""); 

    enum : uintmax_t { multiplier = std::numeric_limits <uintmax_t>::max ()/0xFF }; 
    return static_cast <IntType> (pattern * multiplier); 
}