GCC and Clang provide builtin spellings for operations that their front ends recognize directly. Bit-counting builtins can lower to a target instruction, a library sequence, or an optimized expression depending on the selected target and optimization context. Source code should choose the interface that states its portability and zero-input contract.
GNU-Style Bit Builtins #
Both GCC and Clang support these families for unsigned int, unsigned long, and unsigned long long:
| Operation | unsigned int | unsigned long | unsigned long long |
|---|---|---|---|
| Number of set bits | __builtin_popcount | __builtin_popcountl | __builtin_popcountll |
| Leading zero bits | __builtin_clz | __builtin_clzl | __builtin_clzll |
| Trailing zero bits | __builtin_ctz | __builtin_ctzl | __builtin_ctzll |
The return type is int; the suffix selects the argument width. Cast deliberately instead of relying on integer promotions when the logical width matters. unsigned long has different widths on common ABIs, so use the ll form with an unsigned long long value or a fixed-width standard function when the algorithm requires a particular width.
__builtin_clz(0) and __builtin_ctz(0) have undefined behavior. Test first when zero is possible:
#include <limits>
unsigned trailing_or_width(unsigned value) {
return value == 0 ? std::numeric_limits<unsigned>::digits
: static_cast<unsigned>(__builtin_ctz(value));
}popcount(0) is defined and returns zero, but using a consistent standard interface avoids creating different contracts for related operations.
Prefer C++20 <bit> for Portable C++
#
C++20 supplies std::popcount, std::countl_zero, and std::countr_zero in <bit> for unsigned integer types. Unlike the GNU leading and trailing zero builtins, the counting functions define their result for zero as the number of value bits in the type:
#include <bit>
#include <cstdint>
constexpr std::uint32_t bits = 0b101101;
static_assert(std::popcount(bits) == 4);
static_assert(std::countl_zero(std::uint32_t{0}) == 32);
static_assert(std::countr_zero(std::uint32_t{16}) == 4);Check __cpp_lib_bitops after including <bit> when a library feature boundary must be supported; C++20 specifies the value 201907L for this feature. For compiler-specific code, test builtin availability with __has_builtin only after guarding its use:
#if defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L
// Use std::popcount, std::countl_zero, and std::countr_zero.
#elif defined(__has_builtin)
# if __has_builtin(__builtin_popcount)
# define PROJECT_HAS_BUILTIN_POPCOUNT 1
# endif
#endifGCC and Clang document these particular builtins, so a project that explicitly supports both may instead centralize compiler-family checks and retain a portable fallback for other compilers. Do not assume that __has_builtin exists, and do not use it to select a builtin whose zero precondition the public API does not enforce.
Code Generation and Constant Evaluation #
Inspect generated code for a performance-critical target, because an intrinsic spelling does not promise one instruction. Hardware support, target features, operand width, surrounding control flow, and optimization level determine lowering. Standard <bit> functions give optimizers equivalent semantic information on conforming implementations while retaining portability and use in constant evaluation when their operands and library implementation permit it. A builtin fallback can be warranted for a pre-C++20 library or a measured target gap, but it should remain behind one width-aware wrapper with documented preconditions.
Comments
Read or comment on GitHub (sign-in required to comment)