【问题标题】:Bitswap function using template metaprogramming使用模板元编程的 Bitswap 函数
【发布时间】:2016-09-02 20:09:13
【问题描述】:

bit twiddling hacks 网站提出了以下非常有效的位反转功能:

// Bitswap: reverse the bits of the value of unsigned integral type T
template <class T>
constexpr T bitswap(T src)
{
    constexpr std::size_t char_bit = std::numeric_limits<unsigned char>::digits;
    constexpr std::size_t digits = sizeof(T) * char_bit;
    std::size_t size = digits;
    T mask = ~T();        
    while ((size >>= 1) > 0) {
        mask ^= (mask << size);
        src = ((src >> size) & mask) | ((src << size) & ~mask);
    }
    return src;
}
  • 有没有办法通过使用模板元编程递归来展开循环来加速这个函数?
  • 有没有办法让它与__uint128_t 等扩展类型一起工作? (原版适用于__uint128_t)
  • 如果digits 被正确初始化为正确的位数,该函数理论上是否可以反转具有非两位数的幂的类型的位? (例如假设性的uint41_t)。

【问题讨论】:

  • 您是否检查过 1. 非模板的展开,2. 采用第二个参数digitsleft 的递归展开(可能带有助手)?
  • 现在大多数编译器都知道在使用优化时可以根据需要展开循环,所以我不会担心它,除非它运行得比没有优化时慢。

标签: c++ recursion c++14 bit template-meta-programming


【解决方案1】:

您不能强制推出模板化调用,但很有可能,这将在优化构建中以单个内联函数结束:

#include <iostream>
#include <limits>
#include <iomanip>

template <class T, int size = ((std::numeric_limits<unsigned char>::digits
                                * sizeof(T)) >> 1)>
struct Swap
{
    static constexpr T bitswap(T src, T mask = ~T())
    {
        mask ^= (mask << size);
        src = ((src >> size) & mask) | ((src << size) & ~mask);
        return Swap<T, (size >> 1)>::bitswap(src, mask);
    }
};

template <class T>
struct Swap<T, 0>
{
    static constexpr T bitswap(T src, T mask)
    {
        return src;
    }
};

template <class T>
constexpr T bitswap(T src)
{
    return Swap<T>::bitswap(src);
}

int main() {
    std::cout << std::hex << bitswap(0x12345678l);
}

【讨论】:

  • 您可以使用using discard=int[]; 技巧来避免递归。一些编译器不喜欢内联 32 深的伪递归调用...
  • @Yakk:你的意思是让Swap&lt;T, size, ...&gt; 成为下一步的typedef,将与src 无关的状态存储为模板参数?
  • I implemented discard=int[] 版本避免任何算法递归(只是一些结构递归)。然而,当我们重复O(lg(lg(n)) 次时,这可能是愚蠢的。 :)
【解决方案2】:

这是一个小实用函数,可让您在 C++14 中将内联参数包解压缩为 lambda:

template<class=void, std::size_t...Is>
constexpr auto indexer(std::index_sequence<Is...>) {
  return [](auto&& f) {
    using discard=int[];
    (void)discard{0,(void(
      f(std::integral_constant<std::size_t, Is>{})
    ),0)...};
  };
}
template<std::size_t N>
constexpr auto indexer() {
  return indexer( std::make_index_sequence<N>{} );
}

接下来我们需要一个编译时日志函数:

constexpr std::size_t ct_log_2( std::size_t N ) {
  return (N>1)?1+ct_log_2(N>>1):0;
}

然后我们将它们放在一起:

template <class T>
constexpr T bitswap(T src)
{
  constexpr std::size_t char_bit = std::numeric_limits<unsigned char>::digits;
  static_assert(char_bit == 8);
  constexpr std::size_t digits = sizeof(T) * char_bit;
  T mask = ~T();        

  auto expand = indexer<ct_log_2(digits)>();
  expand([&](auto i){
    constexpr auto size = digits >> (i+1);
    mask ^= (mask << size);
    src = ((src >> size) & mask) | ((src << size) & ~mask);
  });

  return src;
}

遗憾的是,这需要constexpr lambdas 的 C++17 功能。然而,索引器的工作可以变成冗长的手动实现。

创建一个 constexpr 大小计算器:

template<std::size_t digits, std::size_t I>
constexpr auto size_calc = (digits >> (I+1));

将expand 部分替换为:

  using discard=int[];
  (void)discard{0,(void((
    void( mask ^= (mask << size_calc<digits, Is>) ),
    void( src = ( (src >> size_calc<digits, Is> ) & mask ) | ((src << size_calc<digits, Is>) & ~mask) ),
    0
  )),0)...};

我们手动扩展了 expand 为我们所做的事情(不是很丑吗?),然后有一个单参数版本调用:

  return bitswap(src, std::make_index_sequence< ct_log_2(digits) >{} );

具有正确的索引序列。

结果应该是等价的。

Live example.

一些编译器拒绝内联深度递归调用。这里的递归深度为 1 到 3(以获得参数包增长)。现在一个简单的递归解决方案只有 log_2(128) 或 6,所以这可能是矫枉过正。

【讨论】:

  • 在你的例子中 x = 0xBAADF00D 和 r= 700f7aae;但它与 starter post 中的函数结果不同 - 它产生 0xb00fb55d - 看来你的算法是错误的
  • @fsmoke 是的,应该是 auto expand = indexer&lt;ct_log_2(digits)&gt;(); 而不是 -1。固定。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-16
  • 1970-01-01
  • 2012-07-12
  • 2018-11-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多