Следующая шаблонная функция удовлетворяет всем критериям ( live demo ):
template <class T>
constexpr bool HasOneBit (T value)
{
static_assert (std::is_integral<T>::value && !std::is_same<T, bool>::value,
"This function should be used only with integers.");
const std::make_unsigned_t<T> unsignedValue = value;
return unsignedValue != 0 && (unsignedValue & (unsignedValue - 1)) == 0;
}
Это не будет вызывать неопределенное поведение, потому что value
сначала преобразуется в беззнаковый аналог T
. Это преобразование не изменяет представление битов value
.
Я думаю, что соответствующая цитата из стандарта такова (см. N4713 , [conv.integral] # 2):
Если тип назначения не имеет знака, полученное значение представляет собой целое число без знака, соответствующее целому числу источника (по модулю 2 n , где n - количество битов, используемых для представления типа без знака). [ Примечание: В представлении дополнения до двух это преобразование является концептуальным, и в битовой комбинации нет изменений (если нет усечения). - примечание конца ]
A более новая версия этого правила еще проще. Не уверен, относится ли это также к преобразованию без знака в знаковое.
В противном случае результатом является уникальное значение типа назначения, которое совпадает с целым числом источника по модулю 2 N , где N - ширина типа назначения.