Background
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);
}
Live Example.
Attempted workaround
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)
Questions
How undefined is the family of builtin functions clz(0)
and ctz(0)
?
- can they throw an
std::invalid_argument
exception? - for x64, will they for the current gcc distro return the size of the underyling type?
- are the ARM/x86 platforms any different (I have no access to that to test those)?
- is the above SFINAE trick a well-defined way to separate such platforms?