Skip to content

Commit d807661

Browse files
authored
[libc++] Fix bitset conversion functions and refactor constructor (#121348)
This patch addresses several implementation issues in `bitset`'s conversion functions `to_ullong` and `to_ulong`, and refactors its converting constructor `__bitset(unsigned long long __v)` to a more generic and elegant implementation.
1 parent fcd82f9 commit d807661

File tree

3 files changed

+164
-154
lines changed

3 files changed

+164
-154
lines changed

libcxx/include/bitset

Lines changed: 80 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ template <size_t N> struct hash<std::bitset<N>>;
136136
# include <__algorithm/fill.h>
137137
# include <__algorithm/fill_n.h>
138138
# include <__algorithm/find.h>
139+
# include <__algorithm/min.h>
139140
# include <__assert>
140141
# include <__bit/countr.h>
141142
# include <__bit/invert_if.h>
@@ -146,7 +147,11 @@ template <size_t N> struct hash<std::bitset<N>>;
146147
# include <__functional/hash.h>
147148
# include <__functional/identity.h>
148149
# include <__functional/unary_function.h>
150+
# include <__tuple/tuple_indices.h>
151+
# include <__type_traits/enable_if.h>
152+
# include <__type_traits/integral_constant.h>
149153
# include <__type_traits/is_char_like_type.h>
154+
# include <__utility/integer_sequence.h>
150155
# include <climits>
151156
# include <stdexcept>
152157
# include <string_view>
@@ -220,11 +225,42 @@ protected:
220225
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void operator^=(const __bitset& __v) _NOEXCEPT;
221226

222227
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void flip() _NOEXCEPT;
228+
223229
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long to_ulong() const {
224-
return to_ulong(integral_constant < bool, _Size< sizeof(unsigned long) * CHAR_BIT>());
230+
if _LIBCPP_CONSTEXPR (_Size > sizeof(unsigned long) * CHAR_BIT) {
231+
if (auto __e = __make_iter(_Size); std::find(__make_iter(sizeof(unsigned long) * CHAR_BIT), __e, true) != __e)
232+
std::__throw_overflow_error("__bitset<_N_words, _Size>::to_ulong overflow error");
233+
}
234+
235+
static_assert(sizeof(__storage_type) >= sizeof(unsigned long),
236+
"libc++ only supports platforms where sizeof(size_t) >= sizeof(unsigned long), such as 32-bit and "
237+
"64-bit platforms. If you're interested in supporting a platform where that is not the case, please "
238+
"contact the libc++ developers.");
239+
return static_cast<unsigned long>(__first_[0]);
225240
}
241+
226242
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long long to_ullong() const {
227-
return to_ullong(integral_constant < bool, _Size< sizeof(unsigned long long) * CHAR_BIT>());
243+
// Check for overflow if _Size does not fit in unsigned long long
244+
if _LIBCPP_CONSTEXPR (_Size > sizeof(unsigned long long) * CHAR_BIT) {
245+
if (auto __e = __make_iter(_Size);
246+
std::find(__make_iter(sizeof(unsigned long long) * CHAR_BIT), __e, true) != __e)
247+
std::__throw_overflow_error("__bitset<_N_words, _Size>::to_ullong overflow error");
248+
}
249+
250+
// At this point, the effective bitset size (excluding leading zeros) fits in unsigned long long
251+
252+
if _LIBCPP_CONSTEXPR (sizeof(__storage_type) >= sizeof(unsigned long long)) {
253+
// If __storage_type is at least as large as unsigned long long, the result spans only one word
254+
return static_cast<unsigned long long>(__first_[0]);
255+
} else {
256+
// Otherwise, the result spans multiple words which are concatenated
257+
const size_t __ull_words = (sizeof(unsigned long long) - 1) / sizeof(__storage_type) + 1;
258+
const size_t __n_words = _N_words < __ull_words ? _N_words : __ull_words;
259+
unsigned long long __r = static_cast<unsigned long long>(__first_[0]);
260+
for (size_t __i = 1; __i < __n_words; ++__i)
261+
__r |= static_cast<unsigned long long>(__first_[__i]) << (__bits_per_word * __i);
262+
return __r;
263+
}
228264
}
229265

230266
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 bool all() const _NOEXCEPT { return !__scan_bits(__bit_not()); }
@@ -255,16 +291,6 @@ private:
255291
return ~__x;
256292
}
257293
};
258-
# ifdef _LIBCPP_CXX03_LANG
259-
void __init(unsigned long long __v, false_type) _NOEXCEPT;
260-
_LIBCPP_HIDE_FROM_ABI void __init(unsigned long long __v, true_type) _NOEXCEPT;
261-
# endif // _LIBCPP_CXX03_LANG
262-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long to_ulong(false_type) const;
263-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long to_ulong(true_type) const;
264-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long long to_ullong(false_type) const;
265-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long long to_ullong(true_type) const;
266-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long long to_ullong(true_type, false_type) const;
267-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long long to_ullong(true_type, true_type) const;
268294

269295
template <typename _Proj>
270296
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 bool __scan_bits(_Proj __proj) const _NOEXCEPT {
@@ -282,6 +308,15 @@ private:
282308
}
283309
return false;
284310
}
311+
312+
# ifdef _LIBCPP_CXX03_LANG
313+
void __init(unsigned long long __v, false_type) _NOEXCEPT;
314+
_LIBCPP_HIDE_FROM_ABI void __init(unsigned long long __v, true_type) _NOEXCEPT;
315+
# else
316+
template <size_t... _Indices>
317+
_LIBCPP_HIDE_FROM_ABI constexpr __bitset(unsigned long long __v, std::__tuple_indices<_Indices...>) _NOEXCEPT
318+
: __first_{static_cast<__storage_type>(__v >> (_Indices * __bits_per_word))...} {}
319+
# endif // _LIBCPP_CXX03_LANG
285320
};
286321

287322
template <size_t _N_words, size_t _Size>
@@ -316,21 +351,15 @@ inline _LIBCPP_HIDE_FROM_ABI void __bitset<_N_words, _Size>::__init(unsigned lon
316351
template <size_t _N_words, size_t _Size>
317352
inline _LIBCPP_CONSTEXPR __bitset<_N_words, _Size>::__bitset(unsigned long long __v) _NOEXCEPT
318353
# ifndef _LIBCPP_CXX03_LANG
319-
# if __SIZEOF_SIZE_T__ == 8
320-
: __first_{__v}
321-
# elif __SIZEOF_SIZE_T__ == 4
322-
: __first_{static_cast<__storage_type>(__v),
323-
_Size >= 2 * __bits_per_word
324-
? static_cast<__storage_type>(__v >> __bits_per_word)
325-
: static_cast<__storage_type>((__v >> __bits_per_word) &
326-
(__storage_type(1) << (_Size - __bits_per_word)) - 1)}
327-
# else
328-
# error This constructor has not been ported to this platform
329-
# endif
354+
: __bitset(__v,
355+
std::__make_indices_imp< (_N_words < (sizeof(unsigned long long) - 1) / sizeof(__storage_type) + 1)
356+
? _N_words
357+
: (sizeof(unsigned long long) - 1) / sizeof(__storage_type) + 1,
358+
0>{})
330359
# endif
331360
{
332361
# ifdef _LIBCPP_CXX03_LANG
333-
__init(__v, integral_constant<bool, sizeof(unsigned long long) <= sizeof(__storage_type)>());
362+
__init(__v, _BoolConstant<sizeof(unsigned long long) <= sizeof(__storage_type)>());
334363
# endif
335364
}
336365

@@ -369,58 +398,6 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void __bitset<_N_words, _Siz
369398
*__p ^= (__storage_type(1) << __n) - 1;
370399
}
371400

372-
template <size_t _N_words, size_t _Size>
373-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long
374-
__bitset<_N_words, _Size>::to_ulong(false_type) const {
375-
__const_iterator __e = __make_iter(_Size);
376-
__const_iterator __i = std::find(__make_iter(sizeof(unsigned long) * CHAR_BIT), __e, true);
377-
if (__i != __e)
378-
std::__throw_overflow_error("bitset to_ulong overflow error");
379-
380-
return __first_[0];
381-
}
382-
383-
template <size_t _N_words, size_t _Size>
384-
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long
385-
__bitset<_N_words, _Size>::to_ulong(true_type) const {
386-
return __first_[0];
387-
}
388-
389-
template <size_t _N_words, size_t _Size>
390-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long long
391-
__bitset<_N_words, _Size>::to_ullong(false_type) const {
392-
__const_iterator __e = __make_iter(_Size);
393-
__const_iterator __i = std::find(__make_iter(sizeof(unsigned long long) * CHAR_BIT), __e, true);
394-
if (__i != __e)
395-
std::__throw_overflow_error("bitset to_ullong overflow error");
396-
397-
return to_ullong(true_type());
398-
}
399-
400-
template <size_t _N_words, size_t _Size>
401-
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long long
402-
__bitset<_N_words, _Size>::to_ullong(true_type) const {
403-
return to_ullong(true_type(), integral_constant<bool, sizeof(__storage_type) < sizeof(unsigned long long)>());
404-
}
405-
406-
template <size_t _N_words, size_t _Size>
407-
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long long
408-
__bitset<_N_words, _Size>::to_ullong(true_type, false_type) const {
409-
return __first_[0];
410-
}
411-
412-
template <size_t _N_words, size_t _Size>
413-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long long
414-
__bitset<_N_words, _Size>::to_ullong(true_type, true_type) const {
415-
unsigned long long __r = __first_[0];
416-
_LIBCPP_DIAGNOSTIC_PUSH
417-
_LIBCPP_GCC_DIAGNOSTIC_IGNORED("-Wshift-count-overflow")
418-
for (size_t __i = 1; __i < sizeof(unsigned long long) / sizeof(__storage_type); ++__i)
419-
__r |= static_cast<unsigned long long>(__first_[__i]) << (sizeof(__storage_type) * CHAR_BIT);
420-
_LIBCPP_DIAGNOSTIC_POP
421-
return __r;
422-
}
423-
424401
template <size_t _N_words, size_t _Size>
425402
inline size_t __bitset<_N_words, _Size>::__hash_code() const _NOEXCEPT {
426403
size_t __h = 0;
@@ -479,8 +456,25 @@ protected:
479456

480457
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void flip() _NOEXCEPT;
481458

482-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long to_ulong() const;
483-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long long to_ullong() const;
459+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long to_ulong() const {
460+
if _LIBCPP_CONSTEXPR (_Size > sizeof(unsigned long) * CHAR_BIT) {
461+
if (auto __e = __make_iter(_Size); std::find(__make_iter(sizeof(unsigned long) * CHAR_BIT), __e, true) != __e)
462+
__throw_overflow_error("__bitset<1, _Size>::to_ulong overflow error");
463+
}
464+
return static_cast<unsigned long>(__first_);
465+
}
466+
467+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long long to_ullong() const {
468+
// If _Size exceeds the size of unsigned long long, check for overflow
469+
if _LIBCPP_CONSTEXPR (_Size > sizeof(unsigned long long) * CHAR_BIT) {
470+
if (auto __e = __make_iter(_Size);
471+
std::find(__make_iter(sizeof(unsigned long long) * CHAR_BIT), __e, true) != __e)
472+
__throw_overflow_error("__bitset<1, _Size>::to_ullong overflow error");
473+
}
474+
475+
// If _Size fits or no overflow, directly cast to unsigned long long
476+
return static_cast<unsigned long long>(__first_);
477+
}
484478

485479
template <bool _Sparse, class _CharT, class _Traits, class _Allocator>
486480
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 basic_string<_CharT, _Traits, _Allocator>
@@ -507,8 +501,10 @@ inline _LIBCPP_CONSTEXPR __bitset<1, _Size>::__bitset() _NOEXCEPT : __first_(0)
507501

508502
template <size_t _Size>
509503
inline _LIBCPP_CONSTEXPR __bitset<1, _Size>::__bitset(unsigned long long __v) _NOEXCEPT
510-
: __first_(_Size == __bits_per_word ? static_cast<__storage_type>(__v)
511-
: static_cast<__storage_type>(__v) & ((__storage_type(1) << _Size) - 1)) {}
504+
// TODO: We must refer to __bits_per_word in order to work around an issue with the GDB pretty-printers.
505+
// Without it, the pretty-printers complain about a missing __bits_per_word member. This needs to
506+
// be investigated further.
507+
: __first_(_Size == __bits_per_word ? static_cast<__storage_type>(__v) : static_cast<__storage_type>(__v)) {}
512508

513509
template <size_t _Size>
514510
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void
@@ -533,16 +529,6 @@ inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void __bitset<1, _Siz
533529
__first_ ^= ~__storage_type(0) >> (__bits_per_word - _Size);
534530
}
535531

536-
template <size_t _Size>
537-
inline _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long __bitset<1, _Size>::to_ulong() const {
538-
return __first_;
539-
}
540-
541-
template <size_t _Size>
542-
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long long __bitset<1, _Size>::to_ullong() const {
543-
return __first_;
544-
}
545-
546532
template <size_t _Size>
547533
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 bool __bitset<1, _Size>::all() const _NOEXCEPT {
548534
__storage_type __m = ~__storage_type(0) >> (__bits_per_word - _Size);
@@ -633,8 +619,6 @@ class bitset : private __bitset<_Size == 0 ? 0 : (_Size - 1) / (sizeof(size_t) *
633619
public:
634620
static const unsigned __n_words = _Size == 0 ? 0 : (_Size - 1) / (sizeof(size_t) * CHAR_BIT) + 1;
635621
typedef __bitset<__n_words, _Size> __base;
636-
637-
public:
638622
typedef typename __base::reference reference;
639623
typedef typename __base::__const_reference __const_reference;
640624

@@ -713,8 +697,10 @@ public:
713697
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__p < _Size, "bitset::operator[] index out of bounds");
714698
return __base::__make_ref(__p);
715699
}
716-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long to_ulong() const;
717-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long long to_ullong() const;
700+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long to_ulong() const { return __base::to_ulong(); }
701+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long long to_ullong() const {
702+
return __base::to_ullong();
703+
}
718704
template <class _CharT, class _Traits, class _Allocator>
719705
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 basic_string<_CharT, _Traits, _Allocator>
720706
to_string(_CharT __zero = _CharT('0'), _CharT __one = _CharT('1')) const;
@@ -850,16 +836,6 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 bitset<_Size>& bitset<_Size>
850836
return *this;
851837
}
852838

853-
template <size_t _Size>
854-
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long bitset<_Size>::to_ulong() const {
855-
return __base::to_ulong();
856-
}
857-
858-
template <size_t _Size>
859-
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unsigned long long bitset<_Size>::to_ullong() const {
860-
return __base::to_ullong();
861-
}
862-
863839
template <size_t _Size>
864840
template <class _CharT, class _Traits, class _Allocator>
865841
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 basic_string<_CharT, _Traits, _Allocator>

libcxx/test/std/utilities/template.bitset/bitset.members/to_ullong.pass.cpp

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,36 +13,38 @@
1313
#include <type_traits>
1414
#include <climits>
1515
#include <cassert>
16+
#include <stdexcept>
1617

1718
#include "test_macros.h"
1819

1920
template <std::size_t N>
2021
TEST_CONSTEXPR_CXX23 void test_to_ullong() {
21-
const std::size_t M = sizeof(unsigned long long) * CHAR_BIT < N ? sizeof(unsigned long long) * CHAR_BIT : N;
22-
const bool is_M_zero = std::integral_constant<bool, M == 0>::value; // avoid compiler warnings
23-
const std::size_t X = is_M_zero ? sizeof(unsigned long long) * CHAR_BIT - 1 : sizeof(unsigned long long) * CHAR_BIT - M;
24-
const unsigned long long max = is_M_zero ? 0 : (unsigned long long)(-1) >> X;
25-
unsigned long long tests[] = {
26-
0,
27-
std::min<unsigned long long>(1, max),
28-
std::min<unsigned long long>(2, max),
29-
std::min<unsigned long long>(3, max),
30-
std::min(max, max-3),
31-
std::min(max, max-2),
32-
std::min(max, max-1),
33-
max
34-
};
35-
for (unsigned long long j : tests) {
36-
std::bitset<N> v(j);
37-
assert(j == v.to_ullong());
38-
}
39-
{ // test values bigger than can fit into the bitset
40-
const unsigned long long val = 0x55AAAAFFFFAAAA55ULL;
41-
const bool canFit = N < sizeof(unsigned long long) * CHAR_BIT;
42-
const unsigned long long mask = canFit ? (1ULL << (canFit ? N : 0)) - 1 : (unsigned long long)(-1); // avoid compiler warnings
43-
std::bitset<N> v(val);
44-
assert(v.to_ullong() == (val & mask)); // we shouldn't return bit patterns from outside the limits of the bitset.
45-
}
22+
const std::size_t M = sizeof(unsigned long long) * CHAR_BIT < N ? sizeof(unsigned long long) * CHAR_BIT : N;
23+
const bool is_M_zero = std::integral_constant < bool, M == 0 > ::value; // avoid compiler warnings
24+
const std::size_t X =
25+
is_M_zero ? sizeof(unsigned long long) * CHAR_BIT - 1 : sizeof(unsigned long long) * CHAR_BIT - M;
26+
const unsigned long long max = is_M_zero ? 0 : (unsigned long long)(-1) >> X;
27+
unsigned long long tests[] = {
28+
0,
29+
std::min<unsigned long long>(1, max),
30+
std::min<unsigned long long>(2, max),
31+
std::min<unsigned long long>(3, max),
32+
std::min(max, max - 3),
33+
std::min(max, max - 2),
34+
std::min(max, max - 1),
35+
max};
36+
for (unsigned long long j : tests) {
37+
std::bitset<N> v(j);
38+
assert(j == v.to_ullong());
39+
}
40+
{ // test values bigger than can fit into the bitset
41+
const unsigned long long val = 0x55AAAAFFFFAAAA55ULL;
42+
const bool canFit = N < sizeof(unsigned long long) * CHAR_BIT;
43+
const unsigned long long mask =
44+
canFit ? (1ULL << (canFit ? N : 0)) - 1 : (unsigned long long)(-1); // avoid compiler warnings
45+
std::bitset<N> v(val);
46+
assert(v.to_ullong() == (val & mask)); // we shouldn't return bit patterns from outside the limits of the bitset.
47+
}
4648
}
4749

4850
TEST_CONSTEXPR_CXX23 bool test() {
@@ -56,6 +58,22 @@ TEST_CONSTEXPR_CXX23 bool test() {
5658
test_to_ullong<65>();
5759
test_to_ullong<1000>();
5860

61+
#ifndef TEST_HAS_NO_EXCEPTIONS
62+
if (!TEST_IS_CONSTANT_EVALUATED) {
63+
// bitset has true bits beyond the size of unsigned long long
64+
std::bitset<std::numeric_limits<unsigned long long>::digits + 1> q(0);
65+
q.flip();
66+
try {
67+
q.to_ullong(); // throws
68+
assert(false);
69+
} catch (const std::overflow_error&) {
70+
// expected
71+
} catch (...) {
72+
assert(false);
73+
}
74+
}
75+
#endif // TEST_HAS_NO_EXCEPTIONS
76+
5977
return true;
6078
}
6179

0 commit comments

Comments
 (0)