【问题标题】:Compile-time detection of overflow in bitmask位掩码中溢出的编译时检测
【发布时间】:2021-09-04 08:42:24
【问题描述】:

我正在玩一个基于Type-safe Bitmasks in C++ 的通用位掩码类,这是一个最简单的例子来突出我的问题 (Compiler Explorer link here):

#include <type_traits>
#include <cstdint>
#include <iostream>

template<class EnumType,
     // Ensure that Bitmask can only be used with enums
     typename = std::enable_if_t<std::is_enum_v<EnumType>>>
class Bitmask
{
   // Type to store bitmask. Should possibly be bigger
   using underlying_type = std::underlying_type_t<EnumType>;

public:
   constexpr Bitmask(EnumType option) : m_mask(bitmaskValue(option))
   {std::cout << "Bitmask " << (int)m_mask << "\n";}

private:
  // m_mask holds the underlying value, e.g. 2 to the power of enum value underlying_type
  m_mask{0};
  static constexpr underlying_type bitmaskValue(EnumType o)
  { return 1 << static_cast<underlying_type>(o); }

  explicit constexpr Bitmask(underlying_type o) : m_mask(o) {}
};


enum class Option : uint8_t
{
   a, b, c, d, e, f, g, h, i
};
enum class Option2 : int
{
   a, b, c, d, e, f, g, h, i
};

int main()
{
    Bitmask<Option> b1{Option::a};
    Bitmask<Option> b2{Option::h};
    Bitmask<Option> b3{Option::i};
    Bitmask<Option2> b4{Option2::i};
}

// Output
Bitmask 1
Bitmask 128
Bitmask 0
Bitmask 256

简而言之,只要底层类型的位数多于使用的最高枚举值,Bitmask 类型就可以正常工作。否则,bitmaskValue 函数将溢出,不会给出所需的结果,如输出所示,其中 b3 的值为 0,而不是 256。

我当然明白 uint8_t 不能存储超过 8 个不同的位,所以我想要的是某种方法使它成为编译器错误,无论是在声明 Bitmask&lt;Option&gt; 时,还是在使用太高的值设置位掩码时。

我尝试将 bitmaskValue 方法更改为:

   static constexpr underlying_type bitmaskValue(EnumType o) { 
   if constexpr (std::numeric_limits<underlying_type>::digits >= static_cast<underlying_type>(o)) {
       return 1 << static_cast<underlying_type>(o); 
   } else {
       // some error
   }
}

...但是我得到了错误,'o' 不是一个常量表达式。

任何人都可以在这里帮助我正确的方向吗?作为记录,我们的项目目前使用的是 gcc 9.2/c++17,不过我希望我们能尽快升级到 gcc 11.1/c++20。

谢谢。

【问题讨论】:

  • 只有当Bitmask 对象被(或者更确切地说必须是)常量初始化时,你才会得到一个编译时错误。有用吗?
  • 我对@9​​87654329@ 使用了不同的方法(更像是C 位掩码)。不知道我的implementation能不能帮到你(找make_bitmask)。你可以检查一个测试here
  • 您能否尝试在EnumType 上对bitmaskValue 函数进行模板化,而不是将其作为参数传入? template &lt;EnumType o&gt; 应该可以解决问题。

标签: c++ c++17


【解决方案1】:

正如Davis Herring 已经指出的那样,您无法在运行时检查非常量值的溢出(constexpr 标记的函数也可以在运行时执行)。因此,为了获得所需的功能,可以检查模板声明中的溢出,并将值作为非类型模板参数进行测试。

基本思想是brace initialization 带有一个常量值,当强制转换变窄时编译器会产生一个编译错误,例如int8_t{128}; 将失败,因为 128 不能用 int8_t 表示。

template<class EnumType>
class Bitmask
{
   using underlying_type = std::underlying_type_t<EnumType>;

public:
   explicit constexpr Bitmask(underlying_type v) : m_mask{v} {}
   constexpr Bitmask(EnumType option) : m_mask{0x1 << static_cast<underlying_type>(option)}{}

   template <EnumType option> // <- enum value as non-type template parameter
   static constexpr auto make() 
   {
        return Bitmask{underlying_type{0x1 << static_cast<underlying_type>(option)}};
   }
 
   underlying_type m_mask{0};
};
enum class Option : uint8_t
{
   a, b, c, d, e, f, g, h, i
};

Bitmask<Option>::make<Option::f>(); // ok
Bitmask<Option>::make<Option::h>(); // ok
// Bitmask<Option>::make<Option::i>(); // will fail to build

https://godbolt.org/z/117baa4q4

【讨论】:

  • 感谢您的帮助。不幸的是,这会破坏我遗漏的课程的其他部分(举一个最小的例子):请参阅此处添加的“或”创建:godbolt.org/z/qxKdznGMe>
  • 我当然明白我无法在运行时检查非常数的溢出,但这主要(仅?)用于编译时已知的值,就像我提供的示例一样,例如constexpr Bitmask&lt;Option&gt; b1{Option::a}; 在编译时是已知的,但我仍然无法检查它。
【解决方案2】:

如果你也控制枚举定义,最简单的方法就是要求一个枚举常量,在末尾有一个固定的名称(即,它是最大的实值+1),例如EnumType::MAX_ENUM_VALUE

那么你可以拒绝实例化if

EnumType::MAX_ENUM_VALUE > std::numeric_limits<underlying_type>::digits + 1

使用静态断言或 enable_if 或其他。显然,如果枚举常量丢失,这也将无法实例化,因此用户应该很清楚如何修复它。

请注意,这并不是真正的编译时溢出检查,而是溢出的可能性。因此,如果您只使用枚举值的一个子集,这是严格的 - 但它与类型安全的目标是一致的。

【讨论】:

  • 谢谢,我得到了这个工作:godbolt.org/z/n9Wz44r5Y>,甚至可以使用它,虽然我有三个问题:1(我没有让 static_cast 在 enable_if 中工作,并且有使用 c-style-cast),2(必须声明 maxValue 枚举是次优的),3(编译器错误完全不可读)。如果我想要支票,这仍然是迄今为止唯一的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-05-25
  • 1970-01-01
  • 1970-01-01
  • 2018-10-10
  • 1970-01-01
  • 1970-01-01
  • 2021-06-08
相关资源
最近更新 更多