Могу ли я проверить небольшой массив 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 таким образом небезопасно по нескольким причинам:

  1. int не может быть четырьмя байтами, как указывалось в нескольких ответах.
  2. М.М отмечает в комментариях, что bool может быть не одним байтом. Я не знаю каких-либо реальных архитектур, в которых это когда-либо имело место, но, тем не менее, оно является специальным. Он (вероятно) не может быть меньше байта, если компилятор не выполняет очень сложную игру в прятки со своей моделью памяти, а многобайтовый тип bool кажется довольно бесполезным. Однако обратите внимание, что байт не обязательно должен быть 8 бит в первую очередь.
  3. int может иметь представление ловушек. То есть, для определенных битовых шаблонов допустимо вызывать неопределенное поведение, когда они приводятся к int. Это редко встречается в современных архитектурах, но может возникнуть (например) ia64 или в любой системе с подписанными нулями.
  4. Независимо от того, нужно ли вам беспокоиться о каком-либо из вышеперечисленного, ваш код нарушает строгое правило псевдонимов,, поэтому компиляторы могут "оптимизировать" его, предполагая, что 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 соответственно.

Ответ 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()), и поэтому не может быть так же легко оптимизирован.