我可以一次检查一小堆布尔值吗?

Can I check a small array of bools in one go?

本文关键字:布尔值 一次 我可以 检查      更新时间:2023-10-16

这里有一个类似的问题,但这个问题中的用户似乎有一个更大的数组或向量。如果我有:

bool boolArray[4];

我想检查是否所有元素都是假的,我可以分别检查 [ 0 ]、[ 1 ]、[ 2 ] 和 [ 3 ],或者我可以循环访问它。由于(据我所知)false 应该具有值 0 并且 0 以外的任何值都是真的,我想简单地做:

if ( *(int*) boolArray) { }

这有效,但我意识到它依赖于布尔是一个字节,int 是四个字节。如果我投到 (std::uint32_t) 它会没问题,还是仍然是一个坏主意?我只是碰巧在一个数组中有 3 或 4 个布尔值,想知道这是否安全,如果不是,是否有更好的方法可以做到这一点。

另外,如果我最终得到超过 4 个布尔值但少于 8 个布尔值,我可以对 std::uint64_t 或无符号长长或其他东西做同样的事情吗?

正如πάντα ῥεῖ在评论中注意到的那样,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
}

将 4bool秒的类型双关到int可能是一个坏主意 - 您无法确定每种类型的大小。它可能适用于大多数架构,但std::bitset保证在任何情况下都可以在任何地方工作。

几个答案已经解释了好的替代方案,特别是std::bitsetstd::any_of()。我单独写信指出,除非你知道我们不知道的事情,否则以这种方式在boolint之间输入双关语是不安全的,原因如下:

  1. int可能不是四个字节,正如多个答案所指出的那样。
  2. M.M在评论中指出,bool可能不是一个字节。我不知道任何现实世界的架构曾经出现过这种情况,但它仍然是规范合法的。它(可能)不能小于一个字节,除非编译器正在用它的内存模型做一些非常复杂的隐藏球的诡计,而多字节布尔值似乎毫无用处。但请注意,字节首先不必是 8 位。
  3. int可以具有陷阱表示形式。也就是说,某些位模式在转换为int时导致未定义的行为是合法的。这在现代架构中很少见,但可能会出现在(例如)ia64 或任何带有符号零的系统上。
  4. 不管你是否需要担心上述任何一点,你的代码都违反了严格的混叠规则,所以编译器可以自由地"优化"它,假设布尔值和整数是完全独立的对象,具有不重叠的生存期。例如,编译器可能会确定初始化 bool 数组的代码是死存储并消除它,因为在取消引用指针之前的某个时间点,布尔值"必须"不复存在*。还可能出现与寄存器重用和加载/存储重新排序相关的更复杂的情况。所有这些缺陷都是C++标准明确允许的,该标准规定,当您进行这种双关语时,行为是未定义的。

您应该使用其他答案提供的替代解决方案之一。


* 通过将boolArray指向的内存转换为 int 并存储整数来重用该内存是合法的(有一些限制,特别是关于对齐),尽管如果您真的想这样做,则必须通过std::launder传递boolArray,如果您想稍后读取生成的 int。无论如何,编译器有权假设你已经完成了读取,即使你不调用 launder。

您可以使用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如果全部位或全部位都设置为 on,则可以分别使用std::bitset<N>::allstd::bitset<N>::none

标准库以 std::all_of、std::any_of、std::none_of 算法的形式提供您需要的东西。

...对于强制性的"自己滚动"答案,我们可以为任何数组bool[N]提供一个简单的类似"or"的函数,如下所示:

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()),因此不能轻易优化。