Лучшая версия - c ++ 17 :
template< unsigned char... indexes >
constexpr unsigned long long mask(){
return ((1ull<<indexes)|...|0ull);
}
Затем
void apply_known_mask(std::bitset<64> &bits) {
constexpr auto m = mask<B,D,E,H,K,M,L,O>();
bits &= m;
}
обратно c ++ 14 , мыможет сделать этот странный трюк:
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;
}
или, если мы застряли с c ++ 11 , мы можем решить его рекурсивно:
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 со всеми 3 - вы можете переключить определение CPP_VERSION и получить идентичную сборку.
На практике я бы использовал самое современное, какое только мог.14 бьет 11, потому что у нас нет рекурсии и, следовательно, O (n ^ 2) длины символа (что может взорвать время компиляции и использование памяти компилятора);17 побеждает 14, потому что компилятору не нужно «мертвый код» - исключать этот массив, и этот трюк с массивом просто уродлив.
Из этих 14 самый запутанный.Здесь мы создаем анонимный массив всех нулей, а в качестве побочного эффекта создаем наш результат, а затем отбрасываем массив.Отброшенный массив имеет число 0, равное размеру нашей пачки, плюс 1 (который мы добавляем, чтобы мы могли обрабатывать пустые пачки).
Подробное объяснение того, что c ++ 14 версия делает.Это хитрость / хитрость, и тот факт, что вы должны сделать это для расширения пакетов параметров с эффективностью в C ++ 14, является одной из причин, почему сложенные выражения были добавлены в c ++ 17 .
Лучше всего понять изнутри:
r |= (1ull << indexes) // side effect, used
это просто обновляет r
с 1<<indexes
для фиксированного индекса.indexes
- это пакет параметров, поэтому нам придется его расширить.
Остальная часть работы заключается в предоставлении пакета параметров для расширения indexes
внутри.
Один шагout:
(void(
r |= (1ull << indexes) // side effect, used
),0)
здесь мы приводим наше выражение к void
, указывая на то, что нам не важно его возвращаемое значение (нам просто нужен побочный эффект установки r
- в C ++ такие выражения какa |= b
также возвращает значение, которое они устанавливают для a
).
Затем мы используем оператор запятых ,
и 0
, чтобы отбросить void
«значение» и вернуть значение 0
.Итак, это выражение, значение которого равно 0
, и в качестве побочного эффекта вычисления 0
оно устанавливает бит в r
.
int discard[] = {0,(void(
r |= (1ull << indexes) // side effect, used
),0)...};
На этом этапе мы расширяем пакет параметров indexes
.Итак, мы получаем:
{
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),
}
в {}
.Это использование ,
является не оператором запятой, а разделителем элементов массива.Это sizeof...(indexes)+1
0
с, которые также устанавливают биты в r
как побочный эффект.Затем мы присваиваем {}
инструкции построения массива массиву discard
.
Затем мы приводим discard
к void
- большинство компиляторов предупредит вас, если вы создадите переменную и никогда ее не прочитаете.Все компиляторы не будут жаловаться, если вы приведете его к void
, это своего рода способ сказать «Да, я знаю, я не использую это», поэтому он подавляет предупреждение.