2017-07-20 5 views
8

Ich möchte eine Template-Funktion implementieren, die Bitmasken in Kompilierzeit für ganzzahlige Typen generiert. Diese Masken sollten auf 8-Bit-Mustern basieren, wobei das Muster fortlaufend wiederholt wird, um die ganze Zahl zu füllen. Das folgende Beispiel macht genau das, was ich will, aber in Laufzeit:Erstellen einer Bitmaske basierend auf einem Muster als consExpr

#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; 
} 

Der Ausgang des Code oben ist:

Bitmask: 'f0f0f0f0f0f0f0f0' 

Ich weiß, dass das Optimierungsprogramm den gesamten Funktionsaufruf im Code beseitigen oben, aber ich suche nach einer constexpr Lösung mit und wahlweise mit .

Antwort

12

Intuitiv würde ich einen Byte-Repeater machen:

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

Eine einfach zu bedienende Oberfläche:

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

Und das funktioniert in 03 C++. Vielleicht noch älter. Compilation Here.

In neueren Versionen von C++ gibt es wahrscheinlich Möglichkeiten, dies zu vereinfachen. Verdammt, selbst in C++ 03 kann es wahrscheinlich vereinfacht werden.

+0

Ihr Beispiel funktioniert auch mit 'C++ 98'. Ich denke, der 'byte_repeater' ist nicht notwendig, sein' count' Parameter in der primären Vorlage kann initialisiert werden als 'count = sizeof (Int)' dann kann der 'byte_repeater' genauso verwendet werden wie' make_mask' wenn der 'count 'ist der letzte Parameter. Dennoch ist es ein guter Punkt, separate Vorlagen zu machen, um den "count" -Parameter zu verstecken, der nur ein Implementierungsdetail ist. – Akira

+1

Trotz der anderen Antworten war auch hilfreich und schwer zu wählen, die zu akzeptieren, akzeptiere ich diese aus Gründen der Rückwärtskompatibilität. – Akira

4

Sie können es einfach schreiben:

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

Dies funktionieren nur für nicht signierte Typen, aber Sie können es für signierte Typen durch den Aufruf die unsignierte Version und Gießen es funktioniert zu die signierte Typ:

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

Ich mag, wie Ihre Lösung die "signed" und "unsigned" Typen behandelt. Eine sehr einfach zu verstehende Lösung für 'C++ 14' und höher. Dank dafür! – Akira

-3

Benötigen Sie wirklich all diese Komplikationen? Wie wäre es mit einem Makro dafür?

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

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

wird in C++ arbeiten 98 (und im Klar 'c' ohne cout) :-)

oder wenn Sie wirklich wollen c11/++, funktioniert das auch:

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

Dies ist eine beschissene Lösung, aber es funktioniert sicherlich. Ich verstehe nicht, warum es Downvotes gibt. –

+2

@HenriMenke Weil es keine Lösung ist; es tut es nur für einen der Integer-Typen. – Justin

+0

Dies funktioniert für den in der Frage angeforderten Typ. BTW, das ist die effizienteste Lösung aus der Kompilierungseffizienz. Nun, es hat seine eigenen Nachteile, wie kein Scoping, aber es ist leicht und geradlinig. – Serge

2

Wie wäre es, make_mask() als constexpr zu deklarieren, ändern Sie einen Standardparameter, verwenden Sie Shift-Bit, Bit-oder und Rekursion?

ich meine

#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 .: arbeitet mit 11 ++ C zu.

+0

Danke für Ihre Antwort! Ich mag die Einfachheit deines Beispiels. – Akira

4

Ich bin mir nicht sicher, ob es eine klar definierte Lösung für signierte Typen gibt.Für unsigned Typen, würde ich gehen mit:

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

Dies sollte funktionieren, vorausgesetzt, dass std::numeric_limits<Int>::max() ein Vielfaches von std::numeric_limits<unsigned char>::max() ist; Sie könnten eine Überprüfung für die Bedingung std::enable_if hinzufügen und eine andere Lösung verwenden, wenn diese Überprüfung fehlschlägt.

+1

Schöne Lösung! Aber ich nehme an, wenn Sie nach "std :: is_unsigned" suchen, können Sie es vermeiden, nach "std :: is_integral" zu suchen. Ich meine: der SFINAE-Teil könnte vereinfacht werden als 'typenname std :: enable_if :: value, Int> :: type' – max66

+0

Ist' std :: is_unsigned :: value == true' implizieren, dass 'std :: is_integral :: Wert == wahr? Ich kann das glauben, aber ich sah diese Garantie nicht während meiner kurzen Suche. –

+0

Laut ccpreference ist 'std :: is_unsigned'' 'true' 'nur für" vorzeichenlose Integer-Typen und den Typ bool "; vielleicht scheitern für den Typ 'bool'. – max66

1

Es ist nur eine Multiplikation. Außerdem sollten Sie sicherstellen, dass Sie nicht in irgendwelche Fallen laufen:

  • verteidigen gegen is_integral<bool> wahr zu sein
  • diese Funktion eine völlig andere Bedeutung in jeder Maschine hat, die nicht ein 8-Bit-Byte hat , so verweigern nur für jene Maschinen
  • verteidigen gegen unterzeichnet Überlauf zu kompilieren, nur so verwenden uintmax_t

all diese Kontrollen Stuffing auf die Funktion Unterschrift nicht lesbar ist, so verwendet i 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); 
} 
Verwandte Themen