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. The discarded array 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).
A detailed explanation of what the c++14 version is doing. This is a trick/hack, and the fact you have to do this to expand parameters packs with efficiency in C++14 is one of the reasons why fold expressions were added in c++17.
It is best understood from the inside out:
r |= (1ull << indexes) // side effect, used
this just updates r with 1<<indexes for a fixed index. indexes is a parameter pack, so we’ll have to expand it.
The rest of the work is to provide a parameter pack to expand indexes inside of.
One step out:
(void(
r |= (1ull << indexes) // side effect, used
),0)
here we cast our expression to void, indicating we don’t care about its return value (we just want the side effect of setting r — in C++, expressions like a |= b also return the value they set a to).
Then we use the comma operator , and 0 to discard the void “value”, and return the value 0. So this is an expression whose value is 0 and as a side effect of calculating 0 it sets a bit in r.
int discard[] = {0,(void(
r |= (1ull << indexes) // side effect, used
),0)...};
At this point, we expand the parameter pack indexes. So we get:
{
0,
(expression that sets a bit and returns 0),
(expression that sets a bit and returns 0),
[...]
(expression that sets a bit and returns 0),
}
in the {}. This use of , is not the comma operator, but rather the array element separator. This is sizeof...(indexes)+1 0s, which also set bits in r as a side effect. We then assign the {} array construction instructions to an array discard.
Next we cast discard to void — most compilers will warn you if you create a variable and never read it. All compilers will not complain if you cast it to void, it is sort of a way to say “Yes, I know, I’m not using this”, so it suppresses the warning.