Могу ли я проверить небольшой массив bools за один раз?
Был похожий вопрос здесь, но пользователь в этом вопросе, похоже, имел гораздо больший массив или вектор. Если у меня есть:
bool boolArray[4];
И я хочу проверить, являются ли все элементы ложными, я могу проверить [0], [1], [2] и [3] по отдельности, или я могу пройти через это. Поскольку (насколько я знаю) значение false должно иметь значение 0, а все, кроме 0, - true, я подумал о простом выполнении:
if ( *(int*) boolArray) { }
Это работает, но я понимаю, что оно полагается на то, что значение bool составляет один байт, а int - четыре байта. Если я приведу к (std::uint32_t), все будет в порядке, или это все-таки плохая идея? Просто у меня в массиве 3 или 4 bools, и мне было интересно, если это безопасно, а если нет, то есть ли лучший способ сделать это.
Кроме того, в случае, если у меня более 4 бул, но менее 8, могу ли я сделать то же самое с std::uint64_t или без знака long или что-то еще?
Ответы
Ответ 1
Как отмечалось в комментариях πάντα ῥεῖ, std::bitset
, вероятно, является лучшим способом решения этой проблемы без использования UB.
std::bitset<4> boolArray {};
if(boolArray.any()) {
//do the thing
}
Если вы хотите придерживаться массивов, вы можете использовать std::any_of
, но это требует (возможно, свойственно читателям) использования функтора, который просто возвращает свой аргумент:
bool boolArray[4];
if(std::any_of(std::begin(boolArray), std::end(boolArray), [](bool b){return b;}) {
//do the thing
}
Наказание от типа 4 bool
- int
может быть плохой идеей - вы не можете быть уверены в размере каждого из типов. Вероятно, он будет работать на большинстве архитектур, но std::bitset
гарантированно будет работать везде и при любых обстоятельствах.
Ответ 2
Несколько ответов уже объяснили хорошие альтернативы, в частности std::bitset
и std::any_of()
. Я пишу отдельно, чтобы указать, что, если вы не знаете, чего мы не знаем, вводить каламбур между bool
и int
таким образом небезопасно по нескольким причинам:
int
не может быть четырьмя байтами, как указывалось в нескольких ответах.
- М.М отмечает в комментариях, что
bool
может быть не одним байтом. Я не знаю каких-либо реальных архитектур, в которых это когда-либо имело место, но, тем не менее, оно является специальным. Он (вероятно) не может быть меньше байта, если компилятор не выполняет очень сложную игру в прятки со своей моделью памяти, а многобайтовый тип bool кажется довольно бесполезным. Однако обратите внимание, что байт не обязательно должен быть 8 бит в первую очередь.
int
может иметь представление ловушек. То есть, для определенных битовых шаблонов допустимо вызывать неопределенное поведение, когда они приводятся к int
. Это редко встречается в современных архитектурах, но может возникнуть (например) ia64 или в любой системе с подписанными нулями.
- Независимо от того, нужно ли вам беспокоиться о каком-либо из вышеперечисленного, ваш код нарушает строгое правило псевдонимов,, поэтому компиляторы могут "оптимизировать" его, предполагая, что bools и int - это полностью отдельные объекты с непересекающимися временами жизни. Например, компилятор может решить, что код, который инициализирует массив bool, является мертвым хранилищем, и устранить его, потому что bool "должен был" перестать существовать * в какой-то момент до того, как вы разыменовали указатель. Также могут возникнуть более сложные ситуации, связанные с повторным использованием регистра и переупорядочением загрузки/сохранения. Все эти погрешности прямо разрешены стандартом C++, в котором говорится, что поведение не определено, когда вы участвуете в такого рода наказаниях.
Вам следует использовать одно из альтернативных решений, предусмотренных в других ответах.
* Законно (с некоторыми оговорками, особенно в отношении выравнивания) повторно использовать память, указанную в boolArray
, приводя ее к int и сохраняя целое число, хотя, если вы действительно хотите это сделать, вы должны затем передать boolArray
через std::launder
, если вы хотите прочитать полученный int позже. Независимо от этого, компилятор имеет право предполагать, что вы сделали это, когда увидит чтение, даже если вы не вызываете отмывание.
Ответ 3
Вы можете использовать std::bitset<N>::any
:
Любой возвращает true
, если какой-либо из битов установлен в true
, в противном случае false
.
#include <iostream>
#include <bitset>
int main ()
{
std::bitset<4> foo;
// modify foo here
if (foo.any())
std::cout << foo << " has " << foo.count() << " bits set.\n";
else
std::cout << foo << " has no bits set.\n";
return 0;
}
Живой
Если вы хотите вернуть true
, если все или ни один из битов включен, вы можете использовать std::bitset<N>::all
или std::bitset<N>::none
соответственно.
Ответ 4
Стандартная библиотека имеет то, что вам нужно, в форме алгоритмов std::all_of, std::any_of, std::none_of.
Ответ 5
... И для обязательного ответа "накатить свой" мы можем предоставить простую функцию "или" -like для любого массива bool[N]
, например так:
template<size_t N>
constexpr bool or_all(const bool (&bs)[N]) {
for (bool b : bs) {
if (b) { return b; }
}
return false;
}
Или более кратко,
template<size_t N>
constexpr bool or_all(const bool (&bs)[N]) {
for (bool b : bs) { if (b) { return b; } }
return false;
}
Это также дает преимущество как короткого замыкания, подобного ||
, так и полной оптимизации, если его можно вычислить во время компиляции.
Кроме того, если вы хотите изучить первоначальную идею наложения типа bool[N]
на какой-либо другой тип для упрощения наблюдения, я очень рекомендую вам не делать этого, чтобы рассматривать его как char[N2]
вместо этого, где N2 == (sizeof(bool) * N)
. Это позволит вам обеспечить простую программу просмотра представления, которая может автоматически масштабироваться до фактического размера просматриваемого объекта, разрешать итерации по его отдельным байтам и позволять вам легче определять, соответствует ли представление определенным значениям (таким как, например, ноль или не -нуль). Я не совсем уверен, действительно ли такое исследование вызовет какой-либо UB, но я могу с уверенностью сказать, что любая такая конструкция типа не может быть жизнеспособным константным выражением из-за необходимости переосмысления приведения к char*
или unsigned char*
или аналогичный (либо явно, либо в std::memcpy()
), и поэтому не может быть так же легко оптимизирован.