Using MSVC's __popcnt in a constexpr function

Viewed 205

Background: I am trying to repurpose some C++ code written for GCC in a MSVC project. I have been trying to refactor code to make it compatible with MSVC compiler.

Simplified, one of the functions originally was this:

[[nodiscard]] constexpr int count() const noexcept {
    return __builtin_popcountll(mask);//gcc-specific function
}

Where mask is a 64-bit member variable. The obvious conversion to MSVC is:

[[nodiscard]] constexpr int count() const noexcept {
    return __popcnt64(mask); // MSVC replacement
}

However, it doesn't compile because __popcnt64 in not allowed in a constexpr function.

I am using C++17, and I would prefer to avoid having to switch to C++20 if possible.
Is there a way to make it work?

3 Answers

You cannot make a non-constexpr function become a constexpr one. If their standard library doesn't declare it constexpr, then that's it. You will have to write your own, which would be difficult in C++17.

It depends on goal:

  1. Just count bits in compile time and possibly in runtime. Then just implement own constexpr bit counting and don't use __popcnt64. You can look in into Wikipedia's Hamming weight article for ideas.

  2. Use popcnt instruction in runtime. Then you need to implement compile-time / run-time distinction, to use different compile-time and runtime implementations.

For compile-time/runtime distinction in C++20 you would have used if (std::is_constant_evaluated()) { ... } else { ... }

In MSVC, std::is_constant_evaluated is implemented via compiler magic __builtin_is_constant_evaluated(), which happen to compile and work properly in C++17. So you can:

constexpr int popcount(unsigned long long x)
{
    if (__builtin_is_constant_evaluated())
    {
        return -1; // TODO: count bits
    }
    else
    {
        return __popcnt64(x);
    }
}

Note: __builtin_popcountll compiles into either the popcnt instruction or bit counting via bit hacks, depending on compilation flags. MSVC __popcnt64 always compiles into the popcnt instruction. If the goal is to support older CPUs that do not have the popcnt instruction, you'd have to provide CPU detection (compile-time or runtime, again, depending on goals) and fallback, or don't use __popcnt64 at all.

The question has already been answered. So, just some side note.

Building your own and efficient pop function would pobably be the best solution.

And for that you may reuse the very old wisdom from the book "Hacker's Delight" by Henry S. Warren. This book is from the time, when programmers and algorithm developers worked on solutions, to minimize the usage of the precious assembler statements. Both for ROM consumption (yes, indeed) and performance.

You will find there many very efficient solutions, even completely loop free and an astonishingly low number of assembler instructions. For example, with the usage of the divide and conquer method.

It is worth a visit . . .

Related