How do I write a maintainable, fast, compile-time bit-mask in C++?












90















I have some code that is more or less like this:



#include <bitset>

enum Flags { A = 1, B = 2, C = 3, D = 5,
E = 8, F = 13, G = 21, H,
I, J, K, L, M, N, O };

void apply_known_mask(std::bitset<64> &bits) {
const Flags important_bits = { B, D, E, H, K, M, L, O };
std::remove_reference<decltype(bits)>::type mask{};
for (const auto& bit : important_bits) {
mask.set(bit);
}

bits &= mask;
}


Clang >= 3.6 does the smart thing and compiles this to a single and instruction (which then gets inlined everywhere else):



apply_known_mask(std::bitset<64ul>&):  # @apply_known_mask(std::bitset<64ul>&)
and qword ptr [rdi], 775946532
ret


But every version of GCC I've tried compiles this to an enormous mess that includes error handling that should be statically DCE'd. In other code, it will even place the important_bits equivalent as data in line with the code!



.LC0:
.string "bitset::set"
.LC1:
.string "%s: __position (which is %zu) >= _Nb (which is %zu)"
apply_known_mask(std::bitset<64ul>&):
sub rsp, 40
xor esi, esi
mov ecx, 2
movabs rax, 21474836482
mov QWORD PTR [rsp], rax
mov r8d, 1
movabs rax, 94489280520
mov QWORD PTR [rsp+8], rax
movabs rax, 115964117017
mov QWORD PTR [rsp+16], rax
movabs rax, 124554051610
mov QWORD PTR [rsp+24], rax
mov rax, rsp
jmp .L2
.L3:
mov edx, DWORD PTR [rax]
mov rcx, rdx
cmp edx, 63
ja .L7
.L2:
mov rdx, r8
add rax, 4
sal rdx, cl
lea rcx, [rsp+32]
or rsi, rdx
cmp rax, rcx
jne .L3
and QWORD PTR [rdi], rsi
add rsp, 40
ret
.L7:
mov ecx, 64
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:.LC1
xor eax, eax
call std::__throw_out_of_range_fmt(char const*, ...)


How should I write this code so that both compilers can do the right thing? Failing that, how should I write this so that it remains clear, fast, and maintainable?










share|improve this question




















  • 4





    Rather than using a loop, can't you construct a mask with B | D | E | ... | O?

    – HolyBlackCat
    Feb 20 at 12:25






  • 4





    The enum has bit positions rather than already expanded bits, so I could do (1ULL << B) | ... | (1ULL << O)

    – Alex Reinking
    Feb 20 at 12:26






  • 3





    The downside being that the actual names are long and irregular and it's not nearly as easy to see which flags are in the mask with all that line noise.

    – Alex Reinking
    Feb 20 at 12:32






  • 2





    @AlexReinking You could make it one (1ULL << Constant) | per line, and align the constant names on the different lines, that would be easier on the eyes.

    – einpoklum
    Feb 21 at 15:37
















90















I have some code that is more or less like this:



#include <bitset>

enum Flags { A = 1, B = 2, C = 3, D = 5,
E = 8, F = 13, G = 21, H,
I, J, K, L, M, N, O };

void apply_known_mask(std::bitset<64> &bits) {
const Flags important_bits = { B, D, E, H, K, M, L, O };
std::remove_reference<decltype(bits)>::type mask{};
for (const auto& bit : important_bits) {
mask.set(bit);
}

bits &= mask;
}


Clang >= 3.6 does the smart thing and compiles this to a single and instruction (which then gets inlined everywhere else):



apply_known_mask(std::bitset<64ul>&):  # @apply_known_mask(std::bitset<64ul>&)
and qword ptr [rdi], 775946532
ret


But every version of GCC I've tried compiles this to an enormous mess that includes error handling that should be statically DCE'd. In other code, it will even place the important_bits equivalent as data in line with the code!



.LC0:
.string "bitset::set"
.LC1:
.string "%s: __position (which is %zu) >= _Nb (which is %zu)"
apply_known_mask(std::bitset<64ul>&):
sub rsp, 40
xor esi, esi
mov ecx, 2
movabs rax, 21474836482
mov QWORD PTR [rsp], rax
mov r8d, 1
movabs rax, 94489280520
mov QWORD PTR [rsp+8], rax
movabs rax, 115964117017
mov QWORD PTR [rsp+16], rax
movabs rax, 124554051610
mov QWORD PTR [rsp+24], rax
mov rax, rsp
jmp .L2
.L3:
mov edx, DWORD PTR [rax]
mov rcx, rdx
cmp edx, 63
ja .L7
.L2:
mov rdx, r8
add rax, 4
sal rdx, cl
lea rcx, [rsp+32]
or rsi, rdx
cmp rax, rcx
jne .L3
and QWORD PTR [rdi], rsi
add rsp, 40
ret
.L7:
mov ecx, 64
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:.LC1
xor eax, eax
call std::__throw_out_of_range_fmt(char const*, ...)


How should I write this code so that both compilers can do the right thing? Failing that, how should I write this so that it remains clear, fast, and maintainable?










share|improve this question




















  • 4





    Rather than using a loop, can't you construct a mask with B | D | E | ... | O?

    – HolyBlackCat
    Feb 20 at 12:25






  • 4





    The enum has bit positions rather than already expanded bits, so I could do (1ULL << B) | ... | (1ULL << O)

    – Alex Reinking
    Feb 20 at 12:26






  • 3





    The downside being that the actual names are long and irregular and it's not nearly as easy to see which flags are in the mask with all that line noise.

    – Alex Reinking
    Feb 20 at 12:32






  • 2





    @AlexReinking You could make it one (1ULL << Constant) | per line, and align the constant names on the different lines, that would be easier on the eyes.

    – einpoklum
    Feb 21 at 15:37














90












90








90


29






I have some code that is more or less like this:



#include <bitset>

enum Flags { A = 1, B = 2, C = 3, D = 5,
E = 8, F = 13, G = 21, H,
I, J, K, L, M, N, O };

void apply_known_mask(std::bitset<64> &bits) {
const Flags important_bits = { B, D, E, H, K, M, L, O };
std::remove_reference<decltype(bits)>::type mask{};
for (const auto& bit : important_bits) {
mask.set(bit);
}

bits &= mask;
}


Clang >= 3.6 does the smart thing and compiles this to a single and instruction (which then gets inlined everywhere else):



apply_known_mask(std::bitset<64ul>&):  # @apply_known_mask(std::bitset<64ul>&)
and qword ptr [rdi], 775946532
ret


But every version of GCC I've tried compiles this to an enormous mess that includes error handling that should be statically DCE'd. In other code, it will even place the important_bits equivalent as data in line with the code!



.LC0:
.string "bitset::set"
.LC1:
.string "%s: __position (which is %zu) >= _Nb (which is %zu)"
apply_known_mask(std::bitset<64ul>&):
sub rsp, 40
xor esi, esi
mov ecx, 2
movabs rax, 21474836482
mov QWORD PTR [rsp], rax
mov r8d, 1
movabs rax, 94489280520
mov QWORD PTR [rsp+8], rax
movabs rax, 115964117017
mov QWORD PTR [rsp+16], rax
movabs rax, 124554051610
mov QWORD PTR [rsp+24], rax
mov rax, rsp
jmp .L2
.L3:
mov edx, DWORD PTR [rax]
mov rcx, rdx
cmp edx, 63
ja .L7
.L2:
mov rdx, r8
add rax, 4
sal rdx, cl
lea rcx, [rsp+32]
or rsi, rdx
cmp rax, rcx
jne .L3
and QWORD PTR [rdi], rsi
add rsp, 40
ret
.L7:
mov ecx, 64
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:.LC1
xor eax, eax
call std::__throw_out_of_range_fmt(char const*, ...)


How should I write this code so that both compilers can do the right thing? Failing that, how should I write this so that it remains clear, fast, and maintainable?










share|improve this question
















I have some code that is more or less like this:



#include <bitset>

enum Flags { A = 1, B = 2, C = 3, D = 5,
E = 8, F = 13, G = 21, H,
I, J, K, L, M, N, O };

void apply_known_mask(std::bitset<64> &bits) {
const Flags important_bits = { B, D, E, H, K, M, L, O };
std::remove_reference<decltype(bits)>::type mask{};
for (const auto& bit : important_bits) {
mask.set(bit);
}

bits &= mask;
}


Clang >= 3.6 does the smart thing and compiles this to a single and instruction (which then gets inlined everywhere else):



apply_known_mask(std::bitset<64ul>&):  # @apply_known_mask(std::bitset<64ul>&)
and qword ptr [rdi], 775946532
ret


But every version of GCC I've tried compiles this to an enormous mess that includes error handling that should be statically DCE'd. In other code, it will even place the important_bits equivalent as data in line with the code!



.LC0:
.string "bitset::set"
.LC1:
.string "%s: __position (which is %zu) >= _Nb (which is %zu)"
apply_known_mask(std::bitset<64ul>&):
sub rsp, 40
xor esi, esi
mov ecx, 2
movabs rax, 21474836482
mov QWORD PTR [rsp], rax
mov r8d, 1
movabs rax, 94489280520
mov QWORD PTR [rsp+8], rax
movabs rax, 115964117017
mov QWORD PTR [rsp+16], rax
movabs rax, 124554051610
mov QWORD PTR [rsp+24], rax
mov rax, rsp
jmp .L2
.L3:
mov edx, DWORD PTR [rax]
mov rcx, rdx
cmp edx, 63
ja .L7
.L2:
mov rdx, r8
add rax, 4
sal rdx, cl
lea rcx, [rsp+32]
or rsi, rdx
cmp rax, rcx
jne .L3
and QWORD PTR [rdi], rsi
add rsp, 40
ret
.L7:
mov ecx, 64
mov esi, OFFSET FLAT:.LC0
mov edi, OFFSET FLAT:.LC1
xor eax, eax
call std::__throw_out_of_range_fmt(char const*, ...)


How should I write this code so that both compilers can do the right thing? Failing that, how should I write this so that it remains clear, fast, and maintainable?







c++ c++11 bit-manipulation






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Feb 20 at 20:42









grooveplex

1,10911323




1,10911323










asked Feb 20 at 12:20









Alex ReinkingAlex Reinking

3,0861834




3,0861834








  • 4





    Rather than using a loop, can't you construct a mask with B | D | E | ... | O?

    – HolyBlackCat
    Feb 20 at 12:25






  • 4





    The enum has bit positions rather than already expanded bits, so I could do (1ULL << B) | ... | (1ULL << O)

    – Alex Reinking
    Feb 20 at 12:26






  • 3





    The downside being that the actual names are long and irregular and it's not nearly as easy to see which flags are in the mask with all that line noise.

    – Alex Reinking
    Feb 20 at 12:32






  • 2





    @AlexReinking You could make it one (1ULL << Constant) | per line, and align the constant names on the different lines, that would be easier on the eyes.

    – einpoklum
    Feb 21 at 15:37














  • 4





    Rather than using a loop, can't you construct a mask with B | D | E | ... | O?

    – HolyBlackCat
    Feb 20 at 12:25






  • 4





    The enum has bit positions rather than already expanded bits, so I could do (1ULL << B) | ... | (1ULL << O)

    – Alex Reinking
    Feb 20 at 12:26






  • 3





    The downside being that the actual names are long and irregular and it's not nearly as easy to see which flags are in the mask with all that line noise.

    – Alex Reinking
    Feb 20 at 12:32






  • 2





    @AlexReinking You could make it one (1ULL << Constant) | per line, and align the constant names on the different lines, that would be easier on the eyes.

    – einpoklum
    Feb 21 at 15:37








4




4





Rather than using a loop, can't you construct a mask with B | D | E | ... | O?

– HolyBlackCat
Feb 20 at 12:25





Rather than using a loop, can't you construct a mask with B | D | E | ... | O?

– HolyBlackCat
Feb 20 at 12:25




4




4





The enum has bit positions rather than already expanded bits, so I could do (1ULL << B) | ... | (1ULL << O)

– Alex Reinking
Feb 20 at 12:26





The enum has bit positions rather than already expanded bits, so I could do (1ULL << B) | ... | (1ULL << O)

– Alex Reinking
Feb 20 at 12:26




3




3





The downside being that the actual names are long and irregular and it's not nearly as easy to see which flags are in the mask with all that line noise.

– Alex Reinking
Feb 20 at 12:32





The downside being that the actual names are long and irregular and it's not nearly as easy to see which flags are in the mask with all that line noise.

– Alex Reinking
Feb 20 at 12:32




2




2





@AlexReinking You could make it one (1ULL << Constant) | per line, and align the constant names on the different lines, that would be easier on the eyes.

– einpoklum
Feb 21 at 15:37





@AlexReinking You could make it one (1ULL << Constant) | per line, and align the constant names on the different lines, that would be easier on the eyes.

– einpoklum
Feb 21 at 15:37












6 Answers
6






active

oldest

votes


















99














Best version is c++17:



template< unsigned char... indexes >
constexpr unsigned long long mask(){
return ((1ull<<indexes)|...|0ull);
}


Then



void apply_known_mask(std::bitset<64> &bits) {
constexpr auto m = mask<B,D,E,H,K,M,L,O>();
bits &= m;
}


back in c++14, we can do this strange trick:



template< unsigned char... indexes >
constexpr unsigned long long mask(){
auto r = 0ull;
using discard_t = int; // data never used
// value never used:
discard_t discard = {0,(void(
r |= (1ull << indexes) // side effect, used
),0)...};
(void)discard; // block unused var warnings
return r;
}


or, if we are stuck with c++11, we can solve it recursively:



constexpr unsigned long long mask(){
return 0;
}
template<class...Tail>
constexpr unsigned long long mask(unsigned char b0, Tail...tail){
return (1ull<<b0) | mask(tail...);
}
template< unsigned char... indexes >
constexpr unsigned long long mask(){
return mask(indexes...);
}


Godbolt with all 3 -- you can switch CPP_VERSION define, and get identical assembly.



In practice I'd use the most modern I could. 14 beats 11 because we don't have recursion and hence O(n^2) symbol length (which can explode compile time and compiler memory usage); 17 beats 14 because the compiler doesn't have to dead-code-eliminate that array, and that array trick is just ugly.



Of these 14 is the most confusing. Here we create an anonymous array of all 0s, meanwhile as a side effect construct our result, then discard the array. It has a number of 0s in it equal to the size of our pack, plus 1 (which we add so we can handle empty packs).






share|improve this answer





















  • 34





    Sorry, but that C++14 code is something. I don't know what.

    – James
    Feb 20 at 18:34






  • 14





    @James It is a wonderful motivating example of why fold expressions in C++17 are very welcome. It, and similar tricks, turn out to be an efficient way to expand a pack "inplace" without any recursion and that compliers find easy to optimize.

    – Yakk - Adam Nevraumont
    Feb 20 at 19:10








  • 3





    @ruben multi line constexpr is illegal in 11

    – Yakk - Adam Nevraumont
    Feb 20 at 22:40






  • 6





    I can't see myself checking in that C++14 code. I'll stick to the C++11 one since I need that, anyway, but even if I could use it, the C++14 code requires so much explanation I wouldn't. These masks can always be written to have at most 32 elements, so I'm not worried about O(n^2) behavior. After all, if n is bounded by a constant, then it's really O(1). ;)

    – Alex Reinking
    Feb 21 at 7:30








  • 8





    To those trying to understand ((1ull<<indexes)|...|0ull) it is a "fold expression". Specifically it is a "binary right fold" and It should be parsed as (pack op ... op init)

    – Henrik Hansen
    Feb 21 at 10:53



















45














The optimization you're looking for seems to be loop peeling, which is enabled at -O3, or manually with -fpeel-loops. I'm not sure why this falls under the purview of loop peeling rather than loop unrolling, but possibly it's unwilling to unroll a loop with nonlocal control flow inside it (as there is, potentially, from the range check).



By default, though, GCC stops short of being able to peel all the iterations, which apparently is necessary. Experimentally, passing -O2 -fpeel-loops --param max-peeled-insns=200 (the default value is 100) gets the job done with your original code: https://godbolt.org/z/NNWrga






share|improve this answer
























  • You are amazing thank you! I had no idea this was configurable in GCC! Though for some reason -O3 -fpeel-loops --param max-peeled-insns=200 fails... It's due to -ftree-slp-vectorize apparently.

    – Alex Reinking
    Feb 20 at 19:41













  • This solution seems to be limited to the x86-64 target. The output for ARM and ARM64 is still not pretty, which then again might be completely irrelevant for OP.

    – realtime
    Feb 21 at 9:24











  • @realtime - it is somewhat relevant, actually. Thanks for pointing out that it doesn't work in this case. Very disappointing that GCC doesn't catch it before being lowered to a platform-specific IR. LLVM optimizes it before any further lowering.

    – Alex Reinking
    Feb 21 at 11:37



















9














if using only C++11 is a must (&a)[N] is a way to capture arrays. This allows you to write one single recursive function without using helper functions whatsoever:



template <std::size_t N>
constexpr std::uint64_t generate_mask(Flags const (&a)[N], std::size_t i = 0u){
return i < N ? (1ull << a[i] | generate_mask(a, i + 1u)) : 0ull;
}


assigning it to a constexpr auto:



void apply_known_mask(std::bitset<64>& bits) {
constexpr const Flags important_bits = { B, D, E, H, K, M, L, O };
constexpr auto m = generate_mask(important_bits); //< here
bits &= m;
}




Test



int main() {
std::bitset<64> b;
b.flip();
apply_known_mask(b);
std::cout << b.to_string() << 'n';
}


Output



0000000000000000000000000000000000101110010000000000000100100100
// ^ ^^^ ^ ^ ^ ^
// O MLK H E D B


one really has to appreciate C++`s ability to calculate anything turing computable at compile time. It surely still blows my mind (<>).





For the later versions C++14 and C++17 yakk's answer already wonderfully covers that.






share|improve this answer





















  • 3





    How does this demonstrate that apply_known_mask actually optimizes?

    – Alex Reinking
    Feb 20 at 21:50






  • 2





    @AlexReinking: All the scary bits are constexpr. And while that's theoretically not sufficient, we know that GCC is quite capable of evaluating constexpr as intended.

    – MSalters
    Feb 21 at 12:37



















7














I would encourage you to write a proper EnumSet type.



Writing a basic EnumSet<E> in C++14 (onwards) based on std::uint64_t is trivial:



template <typename E>
class EnumSet {
public:
constexpr EnumSet() = default;

constexpr EnumSet(std::initializer_list<E> values) {
for (auto e : values) {
set(e);
}
}

constexpr bool has(E e) const { return mData & mask(e); }

constexpr EnumSet& set(E e) { mData |= mask(e); return *this; }

constexpr EnumSet& unset(E e) { mData &= ~mask(e); return *this; }

constexpr EnumSet& operator&=(const EnumSet& other) {
mData &= other.mData;
return *this;
}

constexpr EnumSet& operator|=(const EnumSet& other) {
mData |= other.mData;
return *this;
}

private:
static constexpr std::uint64_t mask(E e) {
return std::uint64_t(1) << e;
}

std::uint64_t mData = 0;
};


This allows you to write simple code:



void apply_known_mask(EnumSet<Flags>& flags) {
static constexpr EnumSet<Flags> IMPORTANT{ B, D, E, H, K, M, L, O };

flags &= IMPORTANT;
}




In C++11, it requires some convolutions, but remains possible nonetheless:



template <typename E>
class EnumSet {
public:
template <E... Values>
static constexpr EnumSet make() {
return EnumSet(make_impl(Values...));
}

constexpr EnumSet() = default;

constexpr bool has(E e) const { return mData & mask(e); }

void set(E e) { mData |= mask(e); }

void unset(E e) { mData &= ~mask(e); }

EnumSet& operator&=(const EnumSet& other) {
mData &= other.mData;
return *this;
}

EnumSet& operator|=(const EnumSet& other) {
mData |= other.mData;
return *this;
}

private:
static constexpr std::uint64_t mask(E e) {
return std::uint64_t(1) << e;
}

static constexpr std::uint64_t make_impl() { return 0; }

template <typename... Tail>
static constexpr std::uint64_t make_impl(E head, Tail... tail) {
return mask(head) | make_impl(tail...);
}

explicit constexpr EnumSet(std::uint64_t data): mData(data) {}

std::uint64_t mData = 0;
};


And is invoked with:



void apply_known_mask(EnumSet<Flags>& flags) {
static constexpr EnumSet<Flags> IMPORTANT =
EnumSet<Flags>::make<B, D, E, H, K, M, L, O>();

flags &= IMPORTANT;
}


Even GCC trivially generates an and instruction at -O1 godbolt:



apply_known_mask(EnumSet<Flags>&):
and QWORD PTR [rdi], 775946532
ret





share|improve this answer





















  • 1





    In c++11 much of your constexpr code isn't legal. I mean, some has 2 statements! (C++11 constexpr sucked)

    – Yakk - Adam Nevraumont
    Feb 20 at 17:12













  • @Yakk-AdamNevraumont: You did realize that I posted 2 versions of the code, the first for C++14 onward, and a second specially tailored for C++11? (to account for its limitations)

    – Matthieu M.
    Feb 20 at 17:29








  • 1





    It might be better to use std::underlying_type instead of std::uint64_t.

    – James
    Feb 20 at 18:32











  • @James: Actually, no. Do note that EnumSet<E> does not use a value of E as value directly, but instead uses 1 << e. It's a different domain altogether, which is actually what makes the class so valuable => no chance of accidentally indexing by e instead of 1 << e.

    – Matthieu M.
    Feb 20 at 18:53













  • @MatthieuM. Yes you're right. I'm confusing it with our own implementation which is very similar to yours. The disadvantage of using (1 << e) is that if e is out of bounds for the underlying_type's size then it's probably UB, hopefully a compiler error.

    – James
    Feb 20 at 18:55



















6














Since C++11 you could also use classic TMP technique:



template<std::uint64_t Flag, std::uint64_t... Flags>
struct bitmask
{
static constexpr std::uint64_t mask =
bitmask<Flag>::value | bitmask<Flags...>::value;
};

template<std::uint64_t Flag>
struct bitmask<Flag>
{
static constexpr std::uint64_t value = (uint64_t)1 << Flag;
};

void apply_known_mask(std::bitset<64> &bits)
{
constexpr auto mask = bitmask<B, D, E, H, K, M, L, O>::value;
bits &= mask;
}


Link to Compiler Explorer: https://godbolt.org/z/Gk6KX1



The advantage of this approach over template constexpr function is that it's potentially slightly faster to compile due to rule of Chiel.






share|improve this answer































    0














    There are some far to 'clever' ideas here. You are probably not helping maintainability by following them.



    is



    {B, D, E, H, K, M, L, O};


    so much easier to write than



    (B| D| E| H| K| M| L| O);


    ?



    Then none of the rest of the code is needed.






    share|improve this answer



















    • 1





      "B", "D", etc are not flags themselves.

      – Michał Łoś
      Feb 22 at 12:01











    • Yes, you would need to transform these to flags first. That's not at all clear in my answer. sorry. I will update.

      – ANone
      Feb 22 at 12:08











    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54786278%2fhow-do-i-write-a-maintainable-fast-compile-time-bit-mask-in-c%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    6 Answers
    6






    active

    oldest

    votes








    6 Answers
    6






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    99














    Best version is c++17:



    template< unsigned char... indexes >
    constexpr unsigned long long mask(){
    return ((1ull<<indexes)|...|0ull);
    }


    Then



    void apply_known_mask(std::bitset<64> &bits) {
    constexpr auto m = mask<B,D,E,H,K,M,L,O>();
    bits &= m;
    }


    back in c++14, we can do this strange trick:



    template< unsigned char... indexes >
    constexpr unsigned long long mask(){
    auto r = 0ull;
    using discard_t = int; // data never used
    // value never used:
    discard_t discard = {0,(void(
    r |= (1ull << indexes) // side effect, used
    ),0)...};
    (void)discard; // block unused var warnings
    return r;
    }


    or, if we are stuck with c++11, we can solve it recursively:



    constexpr unsigned long long mask(){
    return 0;
    }
    template<class...Tail>
    constexpr unsigned long long mask(unsigned char b0, Tail...tail){
    return (1ull<<b0) | mask(tail...);
    }
    template< unsigned char... indexes >
    constexpr unsigned long long mask(){
    return mask(indexes...);
    }


    Godbolt with all 3 -- you can switch CPP_VERSION define, and get identical assembly.



    In practice I'd use the most modern I could. 14 beats 11 because we don't have recursion and hence O(n^2) symbol length (which can explode compile time and compiler memory usage); 17 beats 14 because the compiler doesn't have to dead-code-eliminate that array, and that array trick is just ugly.



    Of these 14 is the most confusing. Here we create an anonymous array of all 0s, meanwhile as a side effect construct our result, then discard the array. It has a number of 0s in it equal to the size of our pack, plus 1 (which we add so we can handle empty packs).






    share|improve this answer





















    • 34





      Sorry, but that C++14 code is something. I don't know what.

      – James
      Feb 20 at 18:34






    • 14





      @James It is a wonderful motivating example of why fold expressions in C++17 are very welcome. It, and similar tricks, turn out to be an efficient way to expand a pack "inplace" without any recursion and that compliers find easy to optimize.

      – Yakk - Adam Nevraumont
      Feb 20 at 19:10








    • 3





      @ruben multi line constexpr is illegal in 11

      – Yakk - Adam Nevraumont
      Feb 20 at 22:40






    • 6





      I can't see myself checking in that C++14 code. I'll stick to the C++11 one since I need that, anyway, but even if I could use it, the C++14 code requires so much explanation I wouldn't. These masks can always be written to have at most 32 elements, so I'm not worried about O(n^2) behavior. After all, if n is bounded by a constant, then it's really O(1). ;)

      – Alex Reinking
      Feb 21 at 7:30








    • 8





      To those trying to understand ((1ull<<indexes)|...|0ull) it is a "fold expression". Specifically it is a "binary right fold" and It should be parsed as (pack op ... op init)

      – Henrik Hansen
      Feb 21 at 10:53
















    99














    Best version is c++17:



    template< unsigned char... indexes >
    constexpr unsigned long long mask(){
    return ((1ull<<indexes)|...|0ull);
    }


    Then



    void apply_known_mask(std::bitset<64> &bits) {
    constexpr auto m = mask<B,D,E,H,K,M,L,O>();
    bits &= m;
    }


    back in c++14, we can do this strange trick:



    template< unsigned char... indexes >
    constexpr unsigned long long mask(){
    auto r = 0ull;
    using discard_t = int; // data never used
    // value never used:
    discard_t discard = {0,(void(
    r |= (1ull << indexes) // side effect, used
    ),0)...};
    (void)discard; // block unused var warnings
    return r;
    }


    or, if we are stuck with c++11, we can solve it recursively:



    constexpr unsigned long long mask(){
    return 0;
    }
    template<class...Tail>
    constexpr unsigned long long mask(unsigned char b0, Tail...tail){
    return (1ull<<b0) | mask(tail...);
    }
    template< unsigned char... indexes >
    constexpr unsigned long long mask(){
    return mask(indexes...);
    }


    Godbolt with all 3 -- you can switch CPP_VERSION define, and get identical assembly.



    In practice I'd use the most modern I could. 14 beats 11 because we don't have recursion and hence O(n^2) symbol length (which can explode compile time and compiler memory usage); 17 beats 14 because the compiler doesn't have to dead-code-eliminate that array, and that array trick is just ugly.



    Of these 14 is the most confusing. Here we create an anonymous array of all 0s, meanwhile as a side effect construct our result, then discard the array. It has a number of 0s in it equal to the size of our pack, plus 1 (which we add so we can handle empty packs).






    share|improve this answer





















    • 34





      Sorry, but that C++14 code is something. I don't know what.

      – James
      Feb 20 at 18:34






    • 14





      @James It is a wonderful motivating example of why fold expressions in C++17 are very welcome. It, and similar tricks, turn out to be an efficient way to expand a pack "inplace" without any recursion and that compliers find easy to optimize.

      – Yakk - Adam Nevraumont
      Feb 20 at 19:10








    • 3





      @ruben multi line constexpr is illegal in 11

      – Yakk - Adam Nevraumont
      Feb 20 at 22:40






    • 6





      I can't see myself checking in that C++14 code. I'll stick to the C++11 one since I need that, anyway, but even if I could use it, the C++14 code requires so much explanation I wouldn't. These masks can always be written to have at most 32 elements, so I'm not worried about O(n^2) behavior. After all, if n is bounded by a constant, then it's really O(1). ;)

      – Alex Reinking
      Feb 21 at 7:30








    • 8





      To those trying to understand ((1ull<<indexes)|...|0ull) it is a "fold expression". Specifically it is a "binary right fold" and It should be parsed as (pack op ... op init)

      – Henrik Hansen
      Feb 21 at 10:53














    99












    99








    99







    Best version is c++17:



    template< unsigned char... indexes >
    constexpr unsigned long long mask(){
    return ((1ull<<indexes)|...|0ull);
    }


    Then



    void apply_known_mask(std::bitset<64> &bits) {
    constexpr auto m = mask<B,D,E,H,K,M,L,O>();
    bits &= m;
    }


    back in c++14, we can do this strange trick:



    template< unsigned char... indexes >
    constexpr unsigned long long mask(){
    auto r = 0ull;
    using discard_t = int; // data never used
    // value never used:
    discard_t discard = {0,(void(
    r |= (1ull << indexes) // side effect, used
    ),0)...};
    (void)discard; // block unused var warnings
    return r;
    }


    or, if we are stuck with c++11, we can solve it recursively:



    constexpr unsigned long long mask(){
    return 0;
    }
    template<class...Tail>
    constexpr unsigned long long mask(unsigned char b0, Tail...tail){
    return (1ull<<b0) | mask(tail...);
    }
    template< unsigned char... indexes >
    constexpr unsigned long long mask(){
    return mask(indexes...);
    }


    Godbolt with all 3 -- you can switch CPP_VERSION define, and get identical assembly.



    In practice I'd use the most modern I could. 14 beats 11 because we don't have recursion and hence O(n^2) symbol length (which can explode compile time and compiler memory usage); 17 beats 14 because the compiler doesn't have to dead-code-eliminate that array, and that array trick is just ugly.



    Of these 14 is the most confusing. Here we create an anonymous array of all 0s, meanwhile as a side effect construct our result, then discard the array. It has a number of 0s in it equal to the size of our pack, plus 1 (which we add so we can handle empty packs).






    share|improve this answer















    Best version is c++17:



    template< unsigned char... indexes >
    constexpr unsigned long long mask(){
    return ((1ull<<indexes)|...|0ull);
    }


    Then



    void apply_known_mask(std::bitset<64> &bits) {
    constexpr auto m = mask<B,D,E,H,K,M,L,O>();
    bits &= m;
    }


    back in c++14, we can do this strange trick:



    template< unsigned char... indexes >
    constexpr unsigned long long mask(){
    auto r = 0ull;
    using discard_t = int; // data never used
    // value never used:
    discard_t discard = {0,(void(
    r |= (1ull << indexes) // side effect, used
    ),0)...};
    (void)discard; // block unused var warnings
    return r;
    }


    or, if we are stuck with c++11, we can solve it recursively:



    constexpr unsigned long long mask(){
    return 0;
    }
    template<class...Tail>
    constexpr unsigned long long mask(unsigned char b0, Tail...tail){
    return (1ull<<b0) | mask(tail...);
    }
    template< unsigned char... indexes >
    constexpr unsigned long long mask(){
    return mask(indexes...);
    }


    Godbolt with all 3 -- you can switch CPP_VERSION define, and get identical assembly.



    In practice I'd use the most modern I could. 14 beats 11 because we don't have recursion and hence O(n^2) symbol length (which can explode compile time and compiler memory usage); 17 beats 14 because the compiler doesn't have to dead-code-eliminate that array, and that array trick is just ugly.



    Of these 14 is the most confusing. Here we create an anonymous array of all 0s, meanwhile as a side effect construct our result, then discard the array. It has a number of 0s in it equal to the size of our pack, plus 1 (which we add so we can handle empty packs).







    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited Feb 21 at 12:44

























    answered Feb 20 at 12:34









    Yakk - Adam NevraumontYakk - Adam Nevraumont

    186k19198380




    186k19198380








    • 34





      Sorry, but that C++14 code is something. I don't know what.

      – James
      Feb 20 at 18:34






    • 14





      @James It is a wonderful motivating example of why fold expressions in C++17 are very welcome. It, and similar tricks, turn out to be an efficient way to expand a pack "inplace" without any recursion and that compliers find easy to optimize.

      – Yakk - Adam Nevraumont
      Feb 20 at 19:10








    • 3





      @ruben multi line constexpr is illegal in 11

      – Yakk - Adam Nevraumont
      Feb 20 at 22:40






    • 6





      I can't see myself checking in that C++14 code. I'll stick to the C++11 one since I need that, anyway, but even if I could use it, the C++14 code requires so much explanation I wouldn't. These masks can always be written to have at most 32 elements, so I'm not worried about O(n^2) behavior. After all, if n is bounded by a constant, then it's really O(1). ;)

      – Alex Reinking
      Feb 21 at 7:30








    • 8





      To those trying to understand ((1ull<<indexes)|...|0ull) it is a "fold expression". Specifically it is a "binary right fold" and It should be parsed as (pack op ... op init)

      – Henrik Hansen
      Feb 21 at 10:53














    • 34





      Sorry, but that C++14 code is something. I don't know what.

      – James
      Feb 20 at 18:34






    • 14





      @James It is a wonderful motivating example of why fold expressions in C++17 are very welcome. It, and similar tricks, turn out to be an efficient way to expand a pack "inplace" without any recursion and that compliers find easy to optimize.

      – Yakk - Adam Nevraumont
      Feb 20 at 19:10








    • 3





      @ruben multi line constexpr is illegal in 11

      – Yakk - Adam Nevraumont
      Feb 20 at 22:40






    • 6





      I can't see myself checking in that C++14 code. I'll stick to the C++11 one since I need that, anyway, but even if I could use it, the C++14 code requires so much explanation I wouldn't. These masks can always be written to have at most 32 elements, so I'm not worried about O(n^2) behavior. After all, if n is bounded by a constant, then it's really O(1). ;)

      – Alex Reinking
      Feb 21 at 7:30








    • 8





      To those trying to understand ((1ull<<indexes)|...|0ull) it is a "fold expression". Specifically it is a "binary right fold" and It should be parsed as (pack op ... op init)

      – Henrik Hansen
      Feb 21 at 10:53








    34




    34





    Sorry, but that C++14 code is something. I don't know what.

    – James
    Feb 20 at 18:34





    Sorry, but that C++14 code is something. I don't know what.

    – James
    Feb 20 at 18:34




    14




    14





    @James It is a wonderful motivating example of why fold expressions in C++17 are very welcome. It, and similar tricks, turn out to be an efficient way to expand a pack "inplace" without any recursion and that compliers find easy to optimize.

    – Yakk - Adam Nevraumont
    Feb 20 at 19:10







    @James It is a wonderful motivating example of why fold expressions in C++17 are very welcome. It, and similar tricks, turn out to be an efficient way to expand a pack "inplace" without any recursion and that compliers find easy to optimize.

    – Yakk - Adam Nevraumont
    Feb 20 at 19:10






    3




    3





    @ruben multi line constexpr is illegal in 11

    – Yakk - Adam Nevraumont
    Feb 20 at 22:40





    @ruben multi line constexpr is illegal in 11

    – Yakk - Adam Nevraumont
    Feb 20 at 22:40




    6




    6





    I can't see myself checking in that C++14 code. I'll stick to the C++11 one since I need that, anyway, but even if I could use it, the C++14 code requires so much explanation I wouldn't. These masks can always be written to have at most 32 elements, so I'm not worried about O(n^2) behavior. After all, if n is bounded by a constant, then it's really O(1). ;)

    – Alex Reinking
    Feb 21 at 7:30







    I can't see myself checking in that C++14 code. I'll stick to the C++11 one since I need that, anyway, but even if I could use it, the C++14 code requires so much explanation I wouldn't. These masks can always be written to have at most 32 elements, so I'm not worried about O(n^2) behavior. After all, if n is bounded by a constant, then it's really O(1). ;)

    – Alex Reinking
    Feb 21 at 7:30






    8




    8





    To those trying to understand ((1ull<<indexes)|...|0ull) it is a "fold expression". Specifically it is a "binary right fold" and It should be parsed as (pack op ... op init)

    – Henrik Hansen
    Feb 21 at 10:53





    To those trying to understand ((1ull<<indexes)|...|0ull) it is a "fold expression". Specifically it is a "binary right fold" and It should be parsed as (pack op ... op init)

    – Henrik Hansen
    Feb 21 at 10:53













    45














    The optimization you're looking for seems to be loop peeling, which is enabled at -O3, or manually with -fpeel-loops. I'm not sure why this falls under the purview of loop peeling rather than loop unrolling, but possibly it's unwilling to unroll a loop with nonlocal control flow inside it (as there is, potentially, from the range check).



    By default, though, GCC stops short of being able to peel all the iterations, which apparently is necessary. Experimentally, passing -O2 -fpeel-loops --param max-peeled-insns=200 (the default value is 100) gets the job done with your original code: https://godbolt.org/z/NNWrga






    share|improve this answer
























    • You are amazing thank you! I had no idea this was configurable in GCC! Though for some reason -O3 -fpeel-loops --param max-peeled-insns=200 fails... It's due to -ftree-slp-vectorize apparently.

      – Alex Reinking
      Feb 20 at 19:41













    • This solution seems to be limited to the x86-64 target. The output for ARM and ARM64 is still not pretty, which then again might be completely irrelevant for OP.

      – realtime
      Feb 21 at 9:24











    • @realtime - it is somewhat relevant, actually. Thanks for pointing out that it doesn't work in this case. Very disappointing that GCC doesn't catch it before being lowered to a platform-specific IR. LLVM optimizes it before any further lowering.

      – Alex Reinking
      Feb 21 at 11:37
















    45














    The optimization you're looking for seems to be loop peeling, which is enabled at -O3, or manually with -fpeel-loops. I'm not sure why this falls under the purview of loop peeling rather than loop unrolling, but possibly it's unwilling to unroll a loop with nonlocal control flow inside it (as there is, potentially, from the range check).



    By default, though, GCC stops short of being able to peel all the iterations, which apparently is necessary. Experimentally, passing -O2 -fpeel-loops --param max-peeled-insns=200 (the default value is 100) gets the job done with your original code: https://godbolt.org/z/NNWrga






    share|improve this answer
























    • You are amazing thank you! I had no idea this was configurable in GCC! Though for some reason -O3 -fpeel-loops --param max-peeled-insns=200 fails... It's due to -ftree-slp-vectorize apparently.

      – Alex Reinking
      Feb 20 at 19:41













    • This solution seems to be limited to the x86-64 target. The output for ARM and ARM64 is still not pretty, which then again might be completely irrelevant for OP.

      – realtime
      Feb 21 at 9:24











    • @realtime - it is somewhat relevant, actually. Thanks for pointing out that it doesn't work in this case. Very disappointing that GCC doesn't catch it before being lowered to a platform-specific IR. LLVM optimizes it before any further lowering.

      – Alex Reinking
      Feb 21 at 11:37














    45












    45








    45







    The optimization you're looking for seems to be loop peeling, which is enabled at -O3, or manually with -fpeel-loops. I'm not sure why this falls under the purview of loop peeling rather than loop unrolling, but possibly it's unwilling to unroll a loop with nonlocal control flow inside it (as there is, potentially, from the range check).



    By default, though, GCC stops short of being able to peel all the iterations, which apparently is necessary. Experimentally, passing -O2 -fpeel-loops --param max-peeled-insns=200 (the default value is 100) gets the job done with your original code: https://godbolt.org/z/NNWrga






    share|improve this answer













    The optimization you're looking for seems to be loop peeling, which is enabled at -O3, or manually with -fpeel-loops. I'm not sure why this falls under the purview of loop peeling rather than loop unrolling, but possibly it's unwilling to unroll a loop with nonlocal control flow inside it (as there is, potentially, from the range check).



    By default, though, GCC stops short of being able to peel all the iterations, which apparently is necessary. Experimentally, passing -O2 -fpeel-loops --param max-peeled-insns=200 (the default value is 100) gets the job done with your original code: https://godbolt.org/z/NNWrga







    share|improve this answer












    share|improve this answer



    share|improve this answer










    answered Feb 20 at 16:04









    SneftelSneftel

    24.5k64379




    24.5k64379













    • You are amazing thank you! I had no idea this was configurable in GCC! Though for some reason -O3 -fpeel-loops --param max-peeled-insns=200 fails... It's due to -ftree-slp-vectorize apparently.

      – Alex Reinking
      Feb 20 at 19:41













    • This solution seems to be limited to the x86-64 target. The output for ARM and ARM64 is still not pretty, which then again might be completely irrelevant for OP.

      – realtime
      Feb 21 at 9:24











    • @realtime - it is somewhat relevant, actually. Thanks for pointing out that it doesn't work in this case. Very disappointing that GCC doesn't catch it before being lowered to a platform-specific IR. LLVM optimizes it before any further lowering.

      – Alex Reinking
      Feb 21 at 11:37



















    • You are amazing thank you! I had no idea this was configurable in GCC! Though for some reason -O3 -fpeel-loops --param max-peeled-insns=200 fails... It's due to -ftree-slp-vectorize apparently.

      – Alex Reinking
      Feb 20 at 19:41













    • This solution seems to be limited to the x86-64 target. The output for ARM and ARM64 is still not pretty, which then again might be completely irrelevant for OP.

      – realtime
      Feb 21 at 9:24











    • @realtime - it is somewhat relevant, actually. Thanks for pointing out that it doesn't work in this case. Very disappointing that GCC doesn't catch it before being lowered to a platform-specific IR. LLVM optimizes it before any further lowering.

      – Alex Reinking
      Feb 21 at 11:37

















    You are amazing thank you! I had no idea this was configurable in GCC! Though for some reason -O3 -fpeel-loops --param max-peeled-insns=200 fails... It's due to -ftree-slp-vectorize apparently.

    – Alex Reinking
    Feb 20 at 19:41







    You are amazing thank you! I had no idea this was configurable in GCC! Though for some reason -O3 -fpeel-loops --param max-peeled-insns=200 fails... It's due to -ftree-slp-vectorize apparently.

    – Alex Reinking
    Feb 20 at 19:41















    This solution seems to be limited to the x86-64 target. The output for ARM and ARM64 is still not pretty, which then again might be completely irrelevant for OP.

    – realtime
    Feb 21 at 9:24





    This solution seems to be limited to the x86-64 target. The output for ARM and ARM64 is still not pretty, which then again might be completely irrelevant for OP.

    – realtime
    Feb 21 at 9:24













    @realtime - it is somewhat relevant, actually. Thanks for pointing out that it doesn't work in this case. Very disappointing that GCC doesn't catch it before being lowered to a platform-specific IR. LLVM optimizes it before any further lowering.

    – Alex Reinking
    Feb 21 at 11:37





    @realtime - it is somewhat relevant, actually. Thanks for pointing out that it doesn't work in this case. Very disappointing that GCC doesn't catch it before being lowered to a platform-specific IR. LLVM optimizes it before any further lowering.

    – Alex Reinking
    Feb 21 at 11:37











    9














    if using only C++11 is a must (&a)[N] is a way to capture arrays. This allows you to write one single recursive function without using helper functions whatsoever:



    template <std::size_t N>
    constexpr std::uint64_t generate_mask(Flags const (&a)[N], std::size_t i = 0u){
    return i < N ? (1ull << a[i] | generate_mask(a, i + 1u)) : 0ull;
    }


    assigning it to a constexpr auto:



    void apply_known_mask(std::bitset<64>& bits) {
    constexpr const Flags important_bits = { B, D, E, H, K, M, L, O };
    constexpr auto m = generate_mask(important_bits); //< here
    bits &= m;
    }




    Test



    int main() {
    std::bitset<64> b;
    b.flip();
    apply_known_mask(b);
    std::cout << b.to_string() << 'n';
    }


    Output



    0000000000000000000000000000000000101110010000000000000100100100
    // ^ ^^^ ^ ^ ^ ^
    // O MLK H E D B


    one really has to appreciate C++`s ability to calculate anything turing computable at compile time. It surely still blows my mind (<>).





    For the later versions C++14 and C++17 yakk's answer already wonderfully covers that.






    share|improve this answer





















    • 3





      How does this demonstrate that apply_known_mask actually optimizes?

      – Alex Reinking
      Feb 20 at 21:50






    • 2





      @AlexReinking: All the scary bits are constexpr. And while that's theoretically not sufficient, we know that GCC is quite capable of evaluating constexpr as intended.

      – MSalters
      Feb 21 at 12:37
















    9














    if using only C++11 is a must (&a)[N] is a way to capture arrays. This allows you to write one single recursive function without using helper functions whatsoever:



    template <std::size_t N>
    constexpr std::uint64_t generate_mask(Flags const (&a)[N], std::size_t i = 0u){
    return i < N ? (1ull << a[i] | generate_mask(a, i + 1u)) : 0ull;
    }


    assigning it to a constexpr auto:



    void apply_known_mask(std::bitset<64>& bits) {
    constexpr const Flags important_bits = { B, D, E, H, K, M, L, O };
    constexpr auto m = generate_mask(important_bits); //< here
    bits &= m;
    }




    Test



    int main() {
    std::bitset<64> b;
    b.flip();
    apply_known_mask(b);
    std::cout << b.to_string() << 'n';
    }


    Output



    0000000000000000000000000000000000101110010000000000000100100100
    // ^ ^^^ ^ ^ ^ ^
    // O MLK H E D B


    one really has to appreciate C++`s ability to calculate anything turing computable at compile time. It surely still blows my mind (<>).





    For the later versions C++14 and C++17 yakk's answer already wonderfully covers that.






    share|improve this answer





















    • 3





      How does this demonstrate that apply_known_mask actually optimizes?

      – Alex Reinking
      Feb 20 at 21:50






    • 2





      @AlexReinking: All the scary bits are constexpr. And while that's theoretically not sufficient, we know that GCC is quite capable of evaluating constexpr as intended.

      – MSalters
      Feb 21 at 12:37














    9












    9








    9







    if using only C++11 is a must (&a)[N] is a way to capture arrays. This allows you to write one single recursive function without using helper functions whatsoever:



    template <std::size_t N>
    constexpr std::uint64_t generate_mask(Flags const (&a)[N], std::size_t i = 0u){
    return i < N ? (1ull << a[i] | generate_mask(a, i + 1u)) : 0ull;
    }


    assigning it to a constexpr auto:



    void apply_known_mask(std::bitset<64>& bits) {
    constexpr const Flags important_bits = { B, D, E, H, K, M, L, O };
    constexpr auto m = generate_mask(important_bits); //< here
    bits &= m;
    }




    Test



    int main() {
    std::bitset<64> b;
    b.flip();
    apply_known_mask(b);
    std::cout << b.to_string() << 'n';
    }


    Output



    0000000000000000000000000000000000101110010000000000000100100100
    // ^ ^^^ ^ ^ ^ ^
    // O MLK H E D B


    one really has to appreciate C++`s ability to calculate anything turing computable at compile time. It surely still blows my mind (<>).





    For the later versions C++14 and C++17 yakk's answer already wonderfully covers that.






    share|improve this answer















    if using only C++11 is a must (&a)[N] is a way to capture arrays. This allows you to write one single recursive function without using helper functions whatsoever:



    template <std::size_t N>
    constexpr std::uint64_t generate_mask(Flags const (&a)[N], std::size_t i = 0u){
    return i < N ? (1ull << a[i] | generate_mask(a, i + 1u)) : 0ull;
    }


    assigning it to a constexpr auto:



    void apply_known_mask(std::bitset<64>& bits) {
    constexpr const Flags important_bits = { B, D, E, H, K, M, L, O };
    constexpr auto m = generate_mask(important_bits); //< here
    bits &= m;
    }




    Test



    int main() {
    std::bitset<64> b;
    b.flip();
    apply_known_mask(b);
    std::cout << b.to_string() << 'n';
    }


    Output



    0000000000000000000000000000000000101110010000000000000100100100
    // ^ ^^^ ^ ^ ^ ^
    // O MLK H E D B


    one really has to appreciate C++`s ability to calculate anything turing computable at compile time. It surely still blows my mind (<>).





    For the later versions C++14 and C++17 yakk's answer already wonderfully covers that.







    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited Feb 22 at 8:16

























    answered Feb 20 at 13:45









    Stack DannyStack Danny

    1,813623




    1,813623








    • 3





      How does this demonstrate that apply_known_mask actually optimizes?

      – Alex Reinking
      Feb 20 at 21:50






    • 2





      @AlexReinking: All the scary bits are constexpr. And while that's theoretically not sufficient, we know that GCC is quite capable of evaluating constexpr as intended.

      – MSalters
      Feb 21 at 12:37














    • 3





      How does this demonstrate that apply_known_mask actually optimizes?

      – Alex Reinking
      Feb 20 at 21:50






    • 2





      @AlexReinking: All the scary bits are constexpr. And while that's theoretically not sufficient, we know that GCC is quite capable of evaluating constexpr as intended.

      – MSalters
      Feb 21 at 12:37








    3




    3





    How does this demonstrate that apply_known_mask actually optimizes?

    – Alex Reinking
    Feb 20 at 21:50





    How does this demonstrate that apply_known_mask actually optimizes?

    – Alex Reinking
    Feb 20 at 21:50




    2




    2





    @AlexReinking: All the scary bits are constexpr. And while that's theoretically not sufficient, we know that GCC is quite capable of evaluating constexpr as intended.

    – MSalters
    Feb 21 at 12:37





    @AlexReinking: All the scary bits are constexpr. And while that's theoretically not sufficient, we know that GCC is quite capable of evaluating constexpr as intended.

    – MSalters
    Feb 21 at 12:37











    7














    I would encourage you to write a proper EnumSet type.



    Writing a basic EnumSet<E> in C++14 (onwards) based on std::uint64_t is trivial:



    template <typename E>
    class EnumSet {
    public:
    constexpr EnumSet() = default;

    constexpr EnumSet(std::initializer_list<E> values) {
    for (auto e : values) {
    set(e);
    }
    }

    constexpr bool has(E e) const { return mData & mask(e); }

    constexpr EnumSet& set(E e) { mData |= mask(e); return *this; }

    constexpr EnumSet& unset(E e) { mData &= ~mask(e); return *this; }

    constexpr EnumSet& operator&=(const EnumSet& other) {
    mData &= other.mData;
    return *this;
    }

    constexpr EnumSet& operator|=(const EnumSet& other) {
    mData |= other.mData;
    return *this;
    }

    private:
    static constexpr std::uint64_t mask(E e) {
    return std::uint64_t(1) << e;
    }

    std::uint64_t mData = 0;
    };


    This allows you to write simple code:



    void apply_known_mask(EnumSet<Flags>& flags) {
    static constexpr EnumSet<Flags> IMPORTANT{ B, D, E, H, K, M, L, O };

    flags &= IMPORTANT;
    }




    In C++11, it requires some convolutions, but remains possible nonetheless:



    template <typename E>
    class EnumSet {
    public:
    template <E... Values>
    static constexpr EnumSet make() {
    return EnumSet(make_impl(Values...));
    }

    constexpr EnumSet() = default;

    constexpr bool has(E e) const { return mData & mask(e); }

    void set(E e) { mData |= mask(e); }

    void unset(E e) { mData &= ~mask(e); }

    EnumSet& operator&=(const EnumSet& other) {
    mData &= other.mData;
    return *this;
    }

    EnumSet& operator|=(const EnumSet& other) {
    mData |= other.mData;
    return *this;
    }

    private:
    static constexpr std::uint64_t mask(E e) {
    return std::uint64_t(1) << e;
    }

    static constexpr std::uint64_t make_impl() { return 0; }

    template <typename... Tail>
    static constexpr std::uint64_t make_impl(E head, Tail... tail) {
    return mask(head) | make_impl(tail...);
    }

    explicit constexpr EnumSet(std::uint64_t data): mData(data) {}

    std::uint64_t mData = 0;
    };


    And is invoked with:



    void apply_known_mask(EnumSet<Flags>& flags) {
    static constexpr EnumSet<Flags> IMPORTANT =
    EnumSet<Flags>::make<B, D, E, H, K, M, L, O>();

    flags &= IMPORTANT;
    }


    Even GCC trivially generates an and instruction at -O1 godbolt:



    apply_known_mask(EnumSet<Flags>&):
    and QWORD PTR [rdi], 775946532
    ret





    share|improve this answer





















    • 1





      In c++11 much of your constexpr code isn't legal. I mean, some has 2 statements! (C++11 constexpr sucked)

      – Yakk - Adam Nevraumont
      Feb 20 at 17:12













    • @Yakk-AdamNevraumont: You did realize that I posted 2 versions of the code, the first for C++14 onward, and a second specially tailored for C++11? (to account for its limitations)

      – Matthieu M.
      Feb 20 at 17:29








    • 1





      It might be better to use std::underlying_type instead of std::uint64_t.

      – James
      Feb 20 at 18:32











    • @James: Actually, no. Do note that EnumSet<E> does not use a value of E as value directly, but instead uses 1 << e. It's a different domain altogether, which is actually what makes the class so valuable => no chance of accidentally indexing by e instead of 1 << e.

      – Matthieu M.
      Feb 20 at 18:53













    • @MatthieuM. Yes you're right. I'm confusing it with our own implementation which is very similar to yours. The disadvantage of using (1 << e) is that if e is out of bounds for the underlying_type's size then it's probably UB, hopefully a compiler error.

      – James
      Feb 20 at 18:55
















    7














    I would encourage you to write a proper EnumSet type.



    Writing a basic EnumSet<E> in C++14 (onwards) based on std::uint64_t is trivial:



    template <typename E>
    class EnumSet {
    public:
    constexpr EnumSet() = default;

    constexpr EnumSet(std::initializer_list<E> values) {
    for (auto e : values) {
    set(e);
    }
    }

    constexpr bool has(E e) const { return mData & mask(e); }

    constexpr EnumSet& set(E e) { mData |= mask(e); return *this; }

    constexpr EnumSet& unset(E e) { mData &= ~mask(e); return *this; }

    constexpr EnumSet& operator&=(const EnumSet& other) {
    mData &= other.mData;
    return *this;
    }

    constexpr EnumSet& operator|=(const EnumSet& other) {
    mData |= other.mData;
    return *this;
    }

    private:
    static constexpr std::uint64_t mask(E e) {
    return std::uint64_t(1) << e;
    }

    std::uint64_t mData = 0;
    };


    This allows you to write simple code:



    void apply_known_mask(EnumSet<Flags>& flags) {
    static constexpr EnumSet<Flags> IMPORTANT{ B, D, E, H, K, M, L, O };

    flags &= IMPORTANT;
    }




    In C++11, it requires some convolutions, but remains possible nonetheless:



    template <typename E>
    class EnumSet {
    public:
    template <E... Values>
    static constexpr EnumSet make() {
    return EnumSet(make_impl(Values...));
    }

    constexpr EnumSet() = default;

    constexpr bool has(E e) const { return mData & mask(e); }

    void set(E e) { mData |= mask(e); }

    void unset(E e) { mData &= ~mask(e); }

    EnumSet& operator&=(const EnumSet& other) {
    mData &= other.mData;
    return *this;
    }

    EnumSet& operator|=(const EnumSet& other) {
    mData |= other.mData;
    return *this;
    }

    private:
    static constexpr std::uint64_t mask(E e) {
    return std::uint64_t(1) << e;
    }

    static constexpr std::uint64_t make_impl() { return 0; }

    template <typename... Tail>
    static constexpr std::uint64_t make_impl(E head, Tail... tail) {
    return mask(head) | make_impl(tail...);
    }

    explicit constexpr EnumSet(std::uint64_t data): mData(data) {}

    std::uint64_t mData = 0;
    };


    And is invoked with:



    void apply_known_mask(EnumSet<Flags>& flags) {
    static constexpr EnumSet<Flags> IMPORTANT =
    EnumSet<Flags>::make<B, D, E, H, K, M, L, O>();

    flags &= IMPORTANT;
    }


    Even GCC trivially generates an and instruction at -O1 godbolt:



    apply_known_mask(EnumSet<Flags>&):
    and QWORD PTR [rdi], 775946532
    ret





    share|improve this answer





















    • 1





      In c++11 much of your constexpr code isn't legal. I mean, some has 2 statements! (C++11 constexpr sucked)

      – Yakk - Adam Nevraumont
      Feb 20 at 17:12













    • @Yakk-AdamNevraumont: You did realize that I posted 2 versions of the code, the first for C++14 onward, and a second specially tailored for C++11? (to account for its limitations)

      – Matthieu M.
      Feb 20 at 17:29








    • 1





      It might be better to use std::underlying_type instead of std::uint64_t.

      – James
      Feb 20 at 18:32











    • @James: Actually, no. Do note that EnumSet<E> does not use a value of E as value directly, but instead uses 1 << e. It's a different domain altogether, which is actually what makes the class so valuable => no chance of accidentally indexing by e instead of 1 << e.

      – Matthieu M.
      Feb 20 at 18:53













    • @MatthieuM. Yes you're right. I'm confusing it with our own implementation which is very similar to yours. The disadvantage of using (1 << e) is that if e is out of bounds for the underlying_type's size then it's probably UB, hopefully a compiler error.

      – James
      Feb 20 at 18:55














    7












    7








    7







    I would encourage you to write a proper EnumSet type.



    Writing a basic EnumSet<E> in C++14 (onwards) based on std::uint64_t is trivial:



    template <typename E>
    class EnumSet {
    public:
    constexpr EnumSet() = default;

    constexpr EnumSet(std::initializer_list<E> values) {
    for (auto e : values) {
    set(e);
    }
    }

    constexpr bool has(E e) const { return mData & mask(e); }

    constexpr EnumSet& set(E e) { mData |= mask(e); return *this; }

    constexpr EnumSet& unset(E e) { mData &= ~mask(e); return *this; }

    constexpr EnumSet& operator&=(const EnumSet& other) {
    mData &= other.mData;
    return *this;
    }

    constexpr EnumSet& operator|=(const EnumSet& other) {
    mData |= other.mData;
    return *this;
    }

    private:
    static constexpr std::uint64_t mask(E e) {
    return std::uint64_t(1) << e;
    }

    std::uint64_t mData = 0;
    };


    This allows you to write simple code:



    void apply_known_mask(EnumSet<Flags>& flags) {
    static constexpr EnumSet<Flags> IMPORTANT{ B, D, E, H, K, M, L, O };

    flags &= IMPORTANT;
    }




    In C++11, it requires some convolutions, but remains possible nonetheless:



    template <typename E>
    class EnumSet {
    public:
    template <E... Values>
    static constexpr EnumSet make() {
    return EnumSet(make_impl(Values...));
    }

    constexpr EnumSet() = default;

    constexpr bool has(E e) const { return mData & mask(e); }

    void set(E e) { mData |= mask(e); }

    void unset(E e) { mData &= ~mask(e); }

    EnumSet& operator&=(const EnumSet& other) {
    mData &= other.mData;
    return *this;
    }

    EnumSet& operator|=(const EnumSet& other) {
    mData |= other.mData;
    return *this;
    }

    private:
    static constexpr std::uint64_t mask(E e) {
    return std::uint64_t(1) << e;
    }

    static constexpr std::uint64_t make_impl() { return 0; }

    template <typename... Tail>
    static constexpr std::uint64_t make_impl(E head, Tail... tail) {
    return mask(head) | make_impl(tail...);
    }

    explicit constexpr EnumSet(std::uint64_t data): mData(data) {}

    std::uint64_t mData = 0;
    };


    And is invoked with:



    void apply_known_mask(EnumSet<Flags>& flags) {
    static constexpr EnumSet<Flags> IMPORTANT =
    EnumSet<Flags>::make<B, D, E, H, K, M, L, O>();

    flags &= IMPORTANT;
    }


    Even GCC trivially generates an and instruction at -O1 godbolt:



    apply_known_mask(EnumSet<Flags>&):
    and QWORD PTR [rdi], 775946532
    ret





    share|improve this answer















    I would encourage you to write a proper EnumSet type.



    Writing a basic EnumSet<E> in C++14 (onwards) based on std::uint64_t is trivial:



    template <typename E>
    class EnumSet {
    public:
    constexpr EnumSet() = default;

    constexpr EnumSet(std::initializer_list<E> values) {
    for (auto e : values) {
    set(e);
    }
    }

    constexpr bool has(E e) const { return mData & mask(e); }

    constexpr EnumSet& set(E e) { mData |= mask(e); return *this; }

    constexpr EnumSet& unset(E e) { mData &= ~mask(e); return *this; }

    constexpr EnumSet& operator&=(const EnumSet& other) {
    mData &= other.mData;
    return *this;
    }

    constexpr EnumSet& operator|=(const EnumSet& other) {
    mData |= other.mData;
    return *this;
    }

    private:
    static constexpr std::uint64_t mask(E e) {
    return std::uint64_t(1) << e;
    }

    std::uint64_t mData = 0;
    };


    This allows you to write simple code:



    void apply_known_mask(EnumSet<Flags>& flags) {
    static constexpr EnumSet<Flags> IMPORTANT{ B, D, E, H, K, M, L, O };

    flags &= IMPORTANT;
    }




    In C++11, it requires some convolutions, but remains possible nonetheless:



    template <typename E>
    class EnumSet {
    public:
    template <E... Values>
    static constexpr EnumSet make() {
    return EnumSet(make_impl(Values...));
    }

    constexpr EnumSet() = default;

    constexpr bool has(E e) const { return mData & mask(e); }

    void set(E e) { mData |= mask(e); }

    void unset(E e) { mData &= ~mask(e); }

    EnumSet& operator&=(const EnumSet& other) {
    mData &= other.mData;
    return *this;
    }

    EnumSet& operator|=(const EnumSet& other) {
    mData |= other.mData;
    return *this;
    }

    private:
    static constexpr std::uint64_t mask(E e) {
    return std::uint64_t(1) << e;
    }

    static constexpr std::uint64_t make_impl() { return 0; }

    template <typename... Tail>
    static constexpr std::uint64_t make_impl(E head, Tail... tail) {
    return mask(head) | make_impl(tail...);
    }

    explicit constexpr EnumSet(std::uint64_t data): mData(data) {}

    std::uint64_t mData = 0;
    };


    And is invoked with:



    void apply_known_mask(EnumSet<Flags>& flags) {
    static constexpr EnumSet<Flags> IMPORTANT =
    EnumSet<Flags>::make<B, D, E, H, K, M, L, O>();

    flags &= IMPORTANT;
    }


    Even GCC trivially generates an and instruction at -O1 godbolt:



    apply_known_mask(EnumSet<Flags>&):
    and QWORD PTR [rdi], 775946532
    ret






    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited Feb 21 at 10:18

























    answered Feb 20 at 17:00









    Matthieu M.Matthieu M.

    204k29278517




    204k29278517








    • 1





      In c++11 much of your constexpr code isn't legal. I mean, some has 2 statements! (C++11 constexpr sucked)

      – Yakk - Adam Nevraumont
      Feb 20 at 17:12













    • @Yakk-AdamNevraumont: You did realize that I posted 2 versions of the code, the first for C++14 onward, and a second specially tailored for C++11? (to account for its limitations)

      – Matthieu M.
      Feb 20 at 17:29








    • 1





      It might be better to use std::underlying_type instead of std::uint64_t.

      – James
      Feb 20 at 18:32











    • @James: Actually, no. Do note that EnumSet<E> does not use a value of E as value directly, but instead uses 1 << e. It's a different domain altogether, which is actually what makes the class so valuable => no chance of accidentally indexing by e instead of 1 << e.

      – Matthieu M.
      Feb 20 at 18:53













    • @MatthieuM. Yes you're right. I'm confusing it with our own implementation which is very similar to yours. The disadvantage of using (1 << e) is that if e is out of bounds for the underlying_type's size then it's probably UB, hopefully a compiler error.

      – James
      Feb 20 at 18:55














    • 1





      In c++11 much of your constexpr code isn't legal. I mean, some has 2 statements! (C++11 constexpr sucked)

      – Yakk - Adam Nevraumont
      Feb 20 at 17:12













    • @Yakk-AdamNevraumont: You did realize that I posted 2 versions of the code, the first for C++14 onward, and a second specially tailored for C++11? (to account for its limitations)

      – Matthieu M.
      Feb 20 at 17:29








    • 1





      It might be better to use std::underlying_type instead of std::uint64_t.

      – James
      Feb 20 at 18:32











    • @James: Actually, no. Do note that EnumSet<E> does not use a value of E as value directly, but instead uses 1 << e. It's a different domain altogether, which is actually what makes the class so valuable => no chance of accidentally indexing by e instead of 1 << e.

      – Matthieu M.
      Feb 20 at 18:53













    • @MatthieuM. Yes you're right. I'm confusing it with our own implementation which is very similar to yours. The disadvantage of using (1 << e) is that if e is out of bounds for the underlying_type's size then it's probably UB, hopefully a compiler error.

      – James
      Feb 20 at 18:55








    1




    1





    In c++11 much of your constexpr code isn't legal. I mean, some has 2 statements! (C++11 constexpr sucked)

    – Yakk - Adam Nevraumont
    Feb 20 at 17:12







    In c++11 much of your constexpr code isn't legal. I mean, some has 2 statements! (C++11 constexpr sucked)

    – Yakk - Adam Nevraumont
    Feb 20 at 17:12















    @Yakk-AdamNevraumont: You did realize that I posted 2 versions of the code, the first for C++14 onward, and a second specially tailored for C++11? (to account for its limitations)

    – Matthieu M.
    Feb 20 at 17:29







    @Yakk-AdamNevraumont: You did realize that I posted 2 versions of the code, the first for C++14 onward, and a second specially tailored for C++11? (to account for its limitations)

    – Matthieu M.
    Feb 20 at 17:29






    1




    1





    It might be better to use std::underlying_type instead of std::uint64_t.

    – James
    Feb 20 at 18:32





    It might be better to use std::underlying_type instead of std::uint64_t.

    – James
    Feb 20 at 18:32













    @James: Actually, no. Do note that EnumSet<E> does not use a value of E as value directly, but instead uses 1 << e. It's a different domain altogether, which is actually what makes the class so valuable => no chance of accidentally indexing by e instead of 1 << e.

    – Matthieu M.
    Feb 20 at 18:53







    @James: Actually, no. Do note that EnumSet<E> does not use a value of E as value directly, but instead uses 1 << e. It's a different domain altogether, which is actually what makes the class so valuable => no chance of accidentally indexing by e instead of 1 << e.

    – Matthieu M.
    Feb 20 at 18:53















    @MatthieuM. Yes you're right. I'm confusing it with our own implementation which is very similar to yours. The disadvantage of using (1 << e) is that if e is out of bounds for the underlying_type's size then it's probably UB, hopefully a compiler error.

    – James
    Feb 20 at 18:55





    @MatthieuM. Yes you're right. I'm confusing it with our own implementation which is very similar to yours. The disadvantage of using (1 << e) is that if e is out of bounds for the underlying_type's size then it's probably UB, hopefully a compiler error.

    – James
    Feb 20 at 18:55











    6














    Since C++11 you could also use classic TMP technique:



    template<std::uint64_t Flag, std::uint64_t... Flags>
    struct bitmask
    {
    static constexpr std::uint64_t mask =
    bitmask<Flag>::value | bitmask<Flags...>::value;
    };

    template<std::uint64_t Flag>
    struct bitmask<Flag>
    {
    static constexpr std::uint64_t value = (uint64_t)1 << Flag;
    };

    void apply_known_mask(std::bitset<64> &bits)
    {
    constexpr auto mask = bitmask<B, D, E, H, K, M, L, O>::value;
    bits &= mask;
    }


    Link to Compiler Explorer: https://godbolt.org/z/Gk6KX1



    The advantage of this approach over template constexpr function is that it's potentially slightly faster to compile due to rule of Chiel.






    share|improve this answer




























      6














      Since C++11 you could also use classic TMP technique:



      template<std::uint64_t Flag, std::uint64_t... Flags>
      struct bitmask
      {
      static constexpr std::uint64_t mask =
      bitmask<Flag>::value | bitmask<Flags...>::value;
      };

      template<std::uint64_t Flag>
      struct bitmask<Flag>
      {
      static constexpr std::uint64_t value = (uint64_t)1 << Flag;
      };

      void apply_known_mask(std::bitset<64> &bits)
      {
      constexpr auto mask = bitmask<B, D, E, H, K, M, L, O>::value;
      bits &= mask;
      }


      Link to Compiler Explorer: https://godbolt.org/z/Gk6KX1



      The advantage of this approach over template constexpr function is that it's potentially slightly faster to compile due to rule of Chiel.






      share|improve this answer


























        6












        6








        6







        Since C++11 you could also use classic TMP technique:



        template<std::uint64_t Flag, std::uint64_t... Flags>
        struct bitmask
        {
        static constexpr std::uint64_t mask =
        bitmask<Flag>::value | bitmask<Flags...>::value;
        };

        template<std::uint64_t Flag>
        struct bitmask<Flag>
        {
        static constexpr std::uint64_t value = (uint64_t)1 << Flag;
        };

        void apply_known_mask(std::bitset<64> &bits)
        {
        constexpr auto mask = bitmask<B, D, E, H, K, M, L, O>::value;
        bits &= mask;
        }


        Link to Compiler Explorer: https://godbolt.org/z/Gk6KX1



        The advantage of this approach over template constexpr function is that it's potentially slightly faster to compile due to rule of Chiel.






        share|improve this answer













        Since C++11 you could also use classic TMP technique:



        template<std::uint64_t Flag, std::uint64_t... Flags>
        struct bitmask
        {
        static constexpr std::uint64_t mask =
        bitmask<Flag>::value | bitmask<Flags...>::value;
        };

        template<std::uint64_t Flag>
        struct bitmask<Flag>
        {
        static constexpr std::uint64_t value = (uint64_t)1 << Flag;
        };

        void apply_known_mask(std::bitset<64> &bits)
        {
        constexpr auto mask = bitmask<B, D, E, H, K, M, L, O>::value;
        bits &= mask;
        }


        Link to Compiler Explorer: https://godbolt.org/z/Gk6KX1



        The advantage of this approach over template constexpr function is that it's potentially slightly faster to compile due to rule of Chiel.







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Feb 20 at 16:40









        Michał ŁośMichał Łoś

        453311




        453311























            0














            There are some far to 'clever' ideas here. You are probably not helping maintainability by following them.



            is



            {B, D, E, H, K, M, L, O};


            so much easier to write than



            (B| D| E| H| K| M| L| O);


            ?



            Then none of the rest of the code is needed.






            share|improve this answer



















            • 1





              "B", "D", etc are not flags themselves.

              – Michał Łoś
              Feb 22 at 12:01











            • Yes, you would need to transform these to flags first. That's not at all clear in my answer. sorry. I will update.

              – ANone
              Feb 22 at 12:08
















            0














            There are some far to 'clever' ideas here. You are probably not helping maintainability by following them.



            is



            {B, D, E, H, K, M, L, O};


            so much easier to write than



            (B| D| E| H| K| M| L| O);


            ?



            Then none of the rest of the code is needed.






            share|improve this answer



















            • 1





              "B", "D", etc are not flags themselves.

              – Michał Łoś
              Feb 22 at 12:01











            • Yes, you would need to transform these to flags first. That's not at all clear in my answer. sorry. I will update.

              – ANone
              Feb 22 at 12:08














            0












            0








            0







            There are some far to 'clever' ideas here. You are probably not helping maintainability by following them.



            is



            {B, D, E, H, K, M, L, O};


            so much easier to write than



            (B| D| E| H| K| M| L| O);


            ?



            Then none of the rest of the code is needed.






            share|improve this answer













            There are some far to 'clever' ideas here. You are probably not helping maintainability by following them.



            is



            {B, D, E, H, K, M, L, O};


            so much easier to write than



            (B| D| E| H| K| M| L| O);


            ?



            Then none of the rest of the code is needed.







            share|improve this answer












            share|improve this answer



            share|improve this answer










            answered Feb 22 at 11:51









            ANoneANone

            712




            712








            • 1





              "B", "D", etc are not flags themselves.

              – Michał Łoś
              Feb 22 at 12:01











            • Yes, you would need to transform these to flags first. That's not at all clear in my answer. sorry. I will update.

              – ANone
              Feb 22 at 12:08














            • 1





              "B", "D", etc are not flags themselves.

              – Michał Łoś
              Feb 22 at 12:01











            • Yes, you would need to transform these to flags first. That's not at all clear in my answer. sorry. I will update.

              – ANone
              Feb 22 at 12:08








            1




            1





            "B", "D", etc are not flags themselves.

            – Michał Łoś
            Feb 22 at 12:01





            "B", "D", etc are not flags themselves.

            – Michał Łoś
            Feb 22 at 12:01













            Yes, you would need to transform these to flags first. That's not at all clear in my answer. sorry. I will update.

            – ANone
            Feb 22 at 12:08





            Yes, you would need to transform these to flags first. That's not at all clear in my answer. sorry. I will update.

            – ANone
            Feb 22 at 12:08


















            draft saved

            draft discarded




















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54786278%2fhow-do-i-write-a-maintainable-fast-compile-time-bit-mask-in-c%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Biblatex bibliography style without URLs when DOI exists (in Overleaf with Zotero bibliography)

            ComboBox Display Member on multiple fields

            Is it possible to collect Nectar points via Trainline?