基于模式创建位掩码作为 constexpr

Create bitmask based on a pattern as constexpr

本文关键字:掩码 constexpr 创建 于模式 模式      更新时间:2023-10-16

我想实现一个模板函数,该函数在编译时为整型生成位掩码。这些掩码应基于 8 位模式,其中模式将连续重复以填充整数。下面的示例完全符合我的需求,但在运行时:

#include <iostream>
#include <type_traits>
#include <cstring>
template<typename Int>
typename std::enable_if<std::is_integral<Int>::value, Int>::type
make_mask(unsigned char pattern) {
Int output {};
std::memset(&output, pattern, sizeof(Int));
return output;
}
int main() {
auto mask = make_mask<unsigned long>(0xf0);
std::cout << "Bitmask: '" << std::hex << mask << "'" << std::endl;
}

上面代码的输出是:

Bitmask: 'f0f0f0f0f0f0f0f0'

我知道优化器可以消除上面代码中的整个函数调用,但我正在寻找一种使用 c++14 和可选的 c++11 的constexpr解决方案。

直观地说,我会做一个字节中继器:

template<class Int, int count, int byte>
struct byte_repeater;
template<class Int, int byte>
struct byte_repeater<Int, 1, byte> {
static const Int value = byte;
};
template<class Int, int count, int byte>
struct byte_repeater {
static const Int value = (byte_repeater<Int, count-1, byte>::value << CHAR_BIT) | byte;
};

易于使用的界面:

template<class Int, int mask> 
struct make_mask {
static const Int value = byte_repeater<Int, sizeof(Int), mask>::value;
};

这在 C++03 中有效。也许更老。 编译在这里。

在较新版本的C++中,可能有办法使这更简单。哎呀,即使在 C++03 中,它也可能被简化。

我不确定有符号类型是否有明确定义的解决方案。 对于无符号类型,我会选择:

template<class Int>
constexpr typename std::enable_if</* std::is_integral<Int>::value && */ std::is_unsigned<Int>::value,
Int>::type make_mask(const unsigned char pattern) {
return ((std::numeric_limits<Int>::max() / std::numeric_limits<unsigned char>::max()) * pattern);
}

这应该有效,前提是std::numeric_limits<Int>::max()std::numeric_limits<unsigned char>::max()的倍数;您可以在std::enable_if条件中添加一个检查,如果该检查失败,则使用另一种解决方案。

你可以把它写出来:

template<typename Int, typename = std::enable_if_t<std::is_integral<Int>::value>>
constexpr Int make_mask(unsigned char pattern) {
constexpr auto numBytes = sizeof(Int);
Int result = 0;
for (std::size_t i = 0; i < numBytes; i++) {
result |= static_cast<Int>(pattern) << (i*8);
}
return result;
}

演示

这仅适用于无符号类型,但您可以通过调用无符号版本并将其强制转换为有符号类型来使其适用于有符号类型:

template<typename Int, std::enable_if_t<std::is_integral<Int>::value && std::is_unsigned<Int>::value, int> = 0>
constexpr Int make_mask(unsigned char pattern) {
constexpr auto numBytes = sizeof(Int);
Int result = 0;
for (std::size_t i = 0; i < numBytes; i++) {
result |= static_cast<Int>(pattern) << (i*8);
}
return result;
}
template<typename Int, std::enable_if_t<std::is_integral<Int>::value && std::is_signed<Int>::value, int> = 0>
constexpr Int make_mask(unsigned char pattern) {
return static_cast<Int>(make_mask<std::make_unsigned_t<Int>>(pattern));
}

演示

make_mask()声明为constexpr,使用 shift-bit、bit-or 和递归添加默认参数对其进行修改怎么样?

我的意思是

#include <climits>
#include <iostream>
template <typename Int>
constexpr typename std::enable_if<std::is_integral<Int>::value, Int>::type
make_mask (unsigned char pattern, std::size_t dim = sizeof(Int))
{ 
return dim ? ((make_mask<Int>(pattern, dim-1U) << CHAR_BIT) | pattern)
: Int{};
}
int main ()
{
constexpr auto mask = make_mask<unsigned long>(0xf0);
std::cout << "Bitmask: '" << std::hex << mask << "'" << std::endl;
}

附言:也适用于C++11。

这只是一个乘法。此外,您需要确保不会遇到任何陷阱:

  • 防御is_integral<bool>真实
  • 这个函数在任何没有 8 位字节的机器中都有完全不同的含义,所以只需拒绝为这些机器编译
  • 防止签名溢出,因此只需使用uintmax_t

将所有这些检查填充到函数签名上是不可读的,所以我使用了static_assert()

template <typename IntType>
constexpr IntType
make_mask ( unsigned char pattern )
{
static_assert ( CHAR_BIT == 8, "" );
static_assert ( std::is_integral<IntType>::value, "" );
static_assert ( not std::is_same<typename std::decay <IntType>::type, bool>::value, "" );
enum : uintmax_t { multiplier = std::numeric_limits <uintmax_t>::max ( ) / 0xFF };
return static_cast <IntType> ( pattern * multiplier );
}

你真的需要所有这些并发症吗?为此仅使用宏怎么样?

#define PATTERN(A) 0x##A##A##A##A##A##A##A##A
cout << hex << PATTERN(f0) << endl;

将在 C++98 中工作(以及没有 cout 的普通"C"(:-(

或者如果你真的想 C11/++,这也有效:

constexpr long long int pattern(const long long unsigned  pattern)  {
return (pattern << 56) | (pattern << 48) | (pattern << 40) 
| (pattern << 32) | (pattern << 24) | (pattern << 16) 
| (pattern << 8) | pattern ;
}