【问题标题】:Way to effectively call _BitScanReverse or __builtin_clz in constexpr functions?在 constexpr 函数中有效调用 _BitScanReverse 或 __builtin_clz 的方法?
【发布时间】:2018-03-21 03:47:51
【问题描述】:

似乎 _BitScanReverse 尽管是一个内在函数而不是真正的函数,但不能在 Visual C++ 的 constexpr 函数中调用。我知道我可以以更慢的方式自己实现此操作,这对于在编译时对其进行评估的情况很好,但不幸的是它不仅仅是 1 个时钟周期的单 CPU指令 (BSR) 用于在运行时对其进行评估的情况。我还没有在 GCC/Clang 中尝试过 __builtin_clz,但它可能有也可能没有同样的问题,我希望这段代码能够在主要编译器中工作,(对于非 GCC、非 Clang 的回退速度较慢,非 VC 编译器)。

想法/问题:

有没有一种简单的方法可以让函数在编译时评估时使用一个代码块,以便它可以是 constexpr 安全的,并在运行时使用不同的代码块,以便它可以快速? (如果是这样,这也与我的其他几个问题有关。)

或者,有没有办法诱使编译器能够评估 _BitScanReverse 的 constexpr 代码?

附加问题:

是否有计划将其最终添加到 C++ 标准中?他们添加了 std::log2 和 std::ilogb,但它们都通过浮点数,而不是只做一个 BSR(或 CLZ 和 ARM 芯片上的减法)。

【问题讨论】:

  • 我认为使用 GCC 兼容的编译器,您可以使用 __builtin_constant_p。不幸的是,我不知道 msvc 是否有类似的构造。
  • @geza 是的,搜索时,我在 Qt 代码库中发现了一个提交,他们从某些函数中删除了 constexpr,理由是 _BitScanForward,并且他们在 GCC 上使用 __builtin_clz 具有等效功能,所以我认为它们可能适用于 constexpr。但是,那里并没有明确说明,他们也从这些函数中删除了 constexpr,但可能只是为了跨编译器的自我一致性。
  • 这就是为什么语言需要获得新的标准化可移植方式来表达硬件可以有效完成的事情。 Rust 有 foo.leading_zeros() 用于像 i32 这样的原始类型。令人失望的是,C 和 C++ 没有跟上并且仍然没有标准的位扫描或 popcnt 函数。有 POSIX ffs() find first set (man7.org/linux/man-pages/man3/ffs.3.html),但它只是 POSIX,而不是 ISO C 或 C++。一些编译器确实可以识别某些惯用语,因此您有时可以编写看起来像哑循环的内容并将其编译为 popcnt 指令,但 IDK 关于位扫描。
  • 是的,__builtin_clz 在 gcc 的 constexpr 函数中被接受,即使使用 -Wextra -Wpedantic 也不会发出警告。 godbolt.org/g/6cai3A.
  • @PeterCordes C/C++ 甚至没有办法为程序员添加进位 adc,这在 1980 年之前就已用于 x86。

标签: c++ visual-c++ g++ constexpr intrinsics


【解决方案1】:

对于C++20,您可以使用std::bit_width 和其他形式<bit> 标题

template< class T >
constexpr T bit_width(T x) noexcept;
#include <bit>
#include <bitset>
#include <iostream>
#include <array>

using BitArray = std::array<unsigned, 8>;

constexpr BitArray Bits() {
    BitArray bits;
    for (unsigned x{0}; x != 8; ++x)
      bits[x] = std::bit_width(x);
    return bits;
}

auto main() -> int {
    constexpr BitArray bits = Bits();

    for (unsigned x{0}; x != 8; ++x) {
        std::cout
            << "bit_width( "
            << std::bitset<4>{x} << " ) = "
            << bits[x] << " "
            << std::bit_width(x) << '\n';
    }
}

试试Godbolt

【讨论】:

    【解决方案2】:

    有没有一种简单的方法可以让函数在编译时评估时使用一个代码块,以便它可以是 constexpr 安全的,并在运行时使用不同的代码块,以便它可以快速? (如果是这样,这也与我的其他几个问题有关。)

    从 C++20 开始,是的! std::is_constant_evaluated 正是为了这个目的。

    所以你现在可以这样做了:

    #include <type_traits>
    
    constexpr bool my_bitscan_reverse(unsigned long& index, unsigned long mask) {
        if (std::is_constant_evaluated()) {
            // do it in a constexpr-friendly manner
            // who cares if it's not as fast as possible
        } else {
            return _BitScanReverse(&index, mask);
        }
    }
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-12-24
      • 2014-01-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多