基于模式创建constexpr位掩码

10
我希望实现一个模板函数,用于在编译时为整数类型生成位掩码。这些掩码应基于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'

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

6个回答

12

直觉上,我会制作一个字节重复器:

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中,它可能可以被简化。


你的示例也适用于 C++98。我认为 byte_repeater 不是必需的,它在主模板中的 count 参数可以初始化为 count = sizeof(Int),然后如果 count 是最后一个参数,就可以像 make_mask 一样使用 byte_repeater。尽管如此,将 count 参数隐藏在单独的模板中是个好主意,因为这只是一个实现细节。 - Akira
1
尽管其他答案也很有帮助,但很难选择要接受哪一个。我选择这个答案是因为它具备向后兼容性。 - Akira

5

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

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条件中添加一个检查,并使用另一种解决方案。


1
不错的解决方案!但是我想,如果您检查std::is_unsigned,可以避免检查std::is_integral。我的意思是:SFINAE部分可以简化为typename std :: enable_if <std :: is_unsigned <Int> :: value,Int> :: type - max66
std::is_unsigned<Int>::value == true 意味着 std::is_integral<Int>::value == true 吗?我可以相信这一点,但在我的简短搜索中没有看到这个保证。 - Joshua Green
1
非常棒的解决方案!结合@Justin提供的解决方案,它也可以用于有符号整数类型,并且在C++11中也适用。我喜欢你如何使用除法生成模式重复器。 - Akira
1
signed int 的问题在于构造掩码可能会将 1 推入或通过符号位(或仅仅是溢出),从而立即触发未定义行为。 - Joshua Green
假设 std::numeric_limits<unsigned int>::max() > std::numeric_limits<int>::max(),那么你的语句 int foo = int{} | std::numeric_limits<unsigned int>::max(); 将会在 foo 中产生一个实现定义的值,右侧是一个无法适应于 intunsigned int - Joshua Green
显示剩余4条评论

4
您可以直接将其写出:
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));
}

Demo


我喜欢你的解决方案如何处理“有符号”和“无符号”的整数类型。一个非常易于理解的适用于 C++14 及以上版本的解决方案。谢谢! - Akira

2

如果将make_mask()声明为constexpr,并使用移位、按位或和递归来修改它,添加一个默认参数呢?

我的意思是:

#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;
 }

P.S.: 也适用于C++11。


1

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

  • 防止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 );
}

-3

你真的需要所有这些复杂性吗?使用宏怎么样?

#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 ;
}

这是一个糟糕的解决方案,但它肯定有效。我不明白为什么会有人投反对票。 - Henri Menke
2
因为这不是一个解决方案;它只对其中一种整数类型起作用。 - Justin
这适用于问题中请求的类型。顺便说一下,这是编译效率最高的解决方案。当然,它也有自己的缺点,比如没有作用域,但它很轻量级且直接明了。 - Serge
1
OP要求一个“在编译时为整数类型生成位掩码的模板函数”,不是针对特定类型或固定大小类型的。 - max66

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接