For a long time, gcc has been providing a number of builtin bit-twiddling functions, in particular the number of trailing and leading 0-bits (also for long unsigned
and long long unsigned
, which have suffixes l
and ll
):
— Built-in Function:
int __builtin_clz (unsigned int x)
Returns the number of leading 0-bits in
x
, starting at the most significant bit position. Ifx
is 0, the result is undefined.— Built-in Function:
int __builtin_ctz (unsigned int x)
Returns the number of trailing 0-bits in
x
, starting at the least significant bit position. Ifx
is 0, the result is undefined.
On every online (disclaimer: only x64) compiler I tested, however, the result has been that both clz(0)
and ctz(0)
return the number of bits of the underlying builtin type, e.g.
#include <iostream>
#include <limits>
int main()
{
// prints 32 32 32 on most systems
std::cout << std::numeric_limits<unsigned>::digits << " " << __builtin_ctz(0) << " " << __builtin_clz(0);
}
The latest Clang SVN trunk in std=c++1y
mode has made all these functions relaxed C++14 constexpr
, which makes them candidates to use in a SFINAE expression for a wrapper function template around the 3 ctz
/ clz
builtins for unsigned
, unsigned long
, and unsigned long long
template<class T> // wrapper class specialized for u, ul, ull (not shown)
constexpr int ctznz(T x) { return wrapper_class_around_builtin_ctz<T>()(x); }
// overload for platforms where ctznz returns size of underlying type
template<class T>
constexpr auto ctz(T x)
-> typename std::enable_if<ctznz(0) == std::numeric_limits<T>::digits, int>::type
{ return ctznz(x); }
// overload for platforms where ctznz does something else
template<class T>
constexpr auto ctz(T x)
-> typename std::enable_if<ctznz(0) != std::numeric_limits<T>::digits, int>::type
{ return x ? ctznz(x) : std::numeric_limits<T>::digits; }
The gain from this hack is that platforms that give the required result for ctz(0)
can omit an extra conditional to test for x==0
(which might seem a micro-optimization, but when you are already down to the level of builtin bit-twiddling functions, it can make a big difference)
How undefined is the family of builtin functions clz(0)
and ctz(0)
?
std::invalid_argument
exception?Unfortunately, even x86-64 implementations can differ - from Intel's instruction set reference,BSF
and BSR
, with a source operand value of (0)
, leaves the destination undefined, and sets the ZF
(zero flag). So the behaviour may not be consistent between micro-architectures or, say, AMD and Intel. (I believe AMD leaves the destination unmodified.)
The newer LZCNT
and TZCNT
instructions are not ubiquitous. Both are present only as of the Haswell architecture (for Intel).