[PATCH] libstdc++: construct bitset from string_view (P2697) [PR119742]
Jonathan Wakely
jwakely@redhat.com
Wed Jul 2 16:54:21 GMT 2025
On Tue, 1 Jul 2025 at 23:36, Nathan Myers <ncm@cantrip.org> wrote:
>
> Add a bitset constructor from string_view, with other arguments
> matching the constructor from string. Test in ways that exercise
> code paths not checked in existing tests for other constructors.
> Fix existing tests that would fail to detect incorrect exception
> behavior.
>
> libstdc++-v3/ChangeLog:
> PR libstdc++/119742
> * include/bits/version.def: new preprocessor symbol
> * include/bits/version.h: new preprocessor symbol
> * include/std/bitset: new constructor
> * testsuite/20_util/bitset/cons/1.cc: fix
> * testsuite/20_util/bitset/cons/6282.cc: fix
> * testsuite/20_util/bitset/cons/string_view.cc: new tests
> * testsuite/20_util/bitset/cons/string_view_wide.cc: new tests
Full sentences in these ChangeLog lines please, as with the explicit
ctors patch.
> ---
> libstdc++-v3/include/bits/version.def | 8 ++
> libstdc++-v3/include/bits/version.h | 10 ++
> libstdc++-v3/include/std/bitset | 49 +++++++
> .../testsuite/20_util/bitset/cons/1.cc | 1 +
> .../testsuite/20_util/bitset/cons/6282.cc | 5 +-
> .../20_util/bitset/cons/string_view.cc | 132 ++++++++++++++++++
> .../20_util/bitset/cons/string_view_wide.cc | 8 ++
> 7 files changed, 210 insertions(+), 3 deletions(-)
> create mode 100644 libstdc++-v3/testsuite/20_util/bitset/cons/string_view.cc
> create mode 100644 libstdc++-v3/testsuite/20_util/bitset/cons/string_view_wide.cc
>
> diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
> index 880586e9126..d82585e1238 100644
> --- a/libstdc++-v3/include/bits/version.def
> +++ b/libstdc++-v3/include/bits/version.def
> @@ -2012,6 +2012,14 @@ ftms = {
> };
> };
>
> +ftms = {
> + name = bitset // ...construct from string_view
This comment is useful, thanks.
> + values = {
> + v = 202306;
> + cxxmin = 26;
I wondered whether we wanted to use hosted=yes here, because
<string_view> isn't required to be freestanding, but we do define it
unconditionally (and it seems useful to have in a freestanding env).
So not using hosted=yes here is correct.
> + };
> +};
> +
> // Standard test specifications.
> stds[97] = ">= 199711L";
> stds[03] = ">= 199711L";
> diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
> index 4300adb2276..c0d3cbe623f 100644
> --- a/libstdc++-v3/include/bits/version.h
> +++ b/libstdc++-v3/include/bits/version.h
> @@ -2253,4 +2253,14 @@
> #endif /* !defined(__cpp_lib_sstream_from_string_view) && defined(__glibcxx_want_sstream_from_string_view) */
> #undef __glibcxx_want_sstream_from_string_view
>
> +#if !defined(__cpp_lib_bitset)
> +# if (__cplusplus > 202302L)
> +# define __glibcxx_bitset 202306L
> +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_bitset)
> +# define __cpp_lib_bitset 202306L
> +# endif
> +# endif
> +#endif /* !defined(__cpp_lib_bitset) && defined(__glibcxx_want_bitset) */
> +#undef __glibcxx_bitset
> +
> #undef __glibcxx_want_all
> diff --git a/libstdc++-v3/include/std/bitset b/libstdc++-v3/include/std/bitset
> index 8b5d270c2a9..f4947902384 100644
> --- a/libstdc++-v3/include/std/bitset
> +++ b/libstdc++-v3/include/std/bitset
> @@ -61,8 +61,13 @@
> #endif
>
> #define __glibcxx_want_constexpr_bitset
> +#define __glibcxx_want_bitset // ...construct from string_view
> #include <bits/version.h>
>
> +#ifdef __cpp_lib_bitset // ...construct from string_view
> +# include <string_view>
> +#endif
> +
> #define _GLIBCXX_BITSET_BITS_PER_WORD (__CHAR_BIT__ * __SIZEOF_LONG__)
> #define _GLIBCXX_BITSET_WORDS(__n) \
> ((__n) / _GLIBCXX_BITSET_BITS_PER_WORD + \
> @@ -831,6 +836,24 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> }
> #endif // HOSTED
>
> +#ifdef __cpp_lib_bitset
> + template<class _CharT, class _Traits>
I was going to say that this should use 'typename' not 'class' but I
see that <bitset> is consistently "wrong" and uses 'class' everywhere.
Let's not change that now.
> + constexpr void
> + _M_check_initial_position(
> + std::basic_string_view<_CharT, _Traits> __s,
> + typename std::basic_string_view<_CharT, _Traits>::size_type __position
We don't need the std:: qualification on these names. That's also
consistently wrong in this file though. I don't mind if you change it
now for the new constructors, or keep the std:: here and we can fix it
throughout the file in a later commit.
> + ) const
> + {
> +# if _GLIBCXX_HOSTED
We define __throw_out_of_range_fmt for non-hosted, it just calls
terminate() (see include/bits/functexcept.h) so I think we can do this
unconditionally. Otherwise, we wouldn't have these range checks at all
for non-hosted. (Looks as though we should remove a similar HOSTED
check in the bitset(const charT*, ...) constructor).
> + if (__position > __s.size())
> + __throw_out_of_range_fmt(__N("bitset::bitset: __position "
> + "(which is %zu) > __s.size() "
> + "(which is %zu)"),
> + __position, __s.size());
> +# endif
> + }
> +#endif
Do we need this function at all though?
Could we change the existing _M_check_initial_position to be a
function template?
#if _GLIBCXX_HOSTED || __cpp_lib_bitset
template<class _Str>
_GLIBCXX23_CONSTEXPR
void
_M_check_initial_position(const _Str& __s,
size_t __position) const
That will work for strings and string views.
> +
> _GLIBCXX23_CONSTEXPR
> void _M_check(size_t __position, const char *__s) const
> {
> @@ -1008,6 +1031,32 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> }
> #endif // HOSTED
>
> +#ifdef __cpp_lib_bitset
> + /**
> + * Use a subset of a string view.
> + * @param __s A string_view of a sequence of @a 0 and @a 1 characters.
> + * @param __position Index of the first character in @a __s to use.
> + * @param __n The maximum number of characters from @a __s to use.
> + * @throw std::out_of_range If @a __position is bigger the size
> + * of @a __s.
> + * @throw std::invalid_argument If a character appears in the string
> + * which is neither @a 0 nor @a 1.
We can use Markdown in our Doxygen comments these days, so in new
comments I prefer to use `__s` rather than @a __s. I find the Doxygen
commands much more distracting when reading the comments directly in
the source, rather than reading the generated HTML docs.
> + */
> + template<class _CharT, class _Traits>
> + constexpr explicit
> + bitset(std::basic_string_view<_CharT, _Traits> __s,
> + std::basic_string_view<_CharT, _Traits>::size_type __position = 0,
> + std::basic_string_view<_CharT, _Traits>::size_type __n =
> + std::basic_string_view<_CharT, _Traits>::npos,
> + _CharT __zero = _CharT('0'), _CharT __one = _CharT('1'))
> + : _Base()
> + {
> + _M_check_initial_position(__s, __position);
> + _M_copy_from_ptr<_CharT, _Traits>(
> + __s.data(), __s.size(), __position, __n, __zero, __one);
> + }
> +#endif
> +
> #if __cplusplus >= 201103L
> /**
> * Construct from a character %array.
> diff --git a/libstdc++-v3/testsuite/20_util/bitset/cons/1.cc b/libstdc++-v3/testsuite/20_util/bitset/cons/1.cc
> index 0a40b6cd965..6441332c8d6 100644
> --- a/libstdc++-v3/testsuite/20_util/bitset/cons/1.cc
> +++ b/libstdc++-v3/testsuite/20_util/bitset/cons/1.cc
> @@ -47,6 +47,7 @@ void test01(void)
> const size_t n3 = 128;
> try {
> std::bitset<n3> bit03(str01, 5);
> + VERIFY(false);
Thanks for noticing this was missing!
> }
> catch(std::invalid_argument& fail) {
> VERIFY( true );
> diff --git a/libstdc++-v3/testsuite/20_util/bitset/cons/6282.cc b/libstdc++-v3/testsuite/20_util/bitset/cons/6282.cc
> index fafa9fc808b..ee348c35b2a 100644
> --- a/libstdc++-v3/testsuite/20_util/bitset/cons/6282.cc
> +++ b/libstdc++-v3/testsuite/20_util/bitset/cons/6282.cc
> @@ -39,6 +39,8 @@ void test02(void)
> std::bitset<0> z3(std::string("10101010101"));
> VERIFY( z3.any() == false );
>
> + VERIFY( z1.to_ulong() == 0 );
> + VERIFY( (z1.to_string<char,char_traits<char>,allocator<char> >().empty() ));
> try {
> z1.set(0);
> VERIFY( false );
> @@ -49,9 +51,6 @@ void test02(void)
> catch(...) {
> VERIFY( false );
> }
> -
> - VERIFY( z1.to_ulong() == 0 );
> - VERIFY( (z1.to_string<char,char_traits<char>,allocator<char> >().empty() ));
> }
>
> int main()
> diff --git a/libstdc++-v3/testsuite/20_util/bitset/cons/string_view.cc b/libstdc++-v3/testsuite/20_util/bitset/cons/string_view.cc
> new file mode 100644
> index 00000000000..ec3a6c86b68
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/bitset/cons/string_view.cc
> @@ -0,0 +1,132 @@
> +// C++26 [bitset.cons]
> +
> +// { dg-do run { target c++26 } }
> +
> +#ifndef C
> +# define C char
> +# define L(s) s
> +#endif
> +
> +#include <bitset>
> +#include <string>
> +#include <string_view>
> +#include <algorithm> // std::reverse, std::transform
> +#include <stdexcept>
> +#include <testsuite_hooks.h>
> +
> +void test01()
> +{
> + // template<_C,_T>
> + // constexpr explicit
> + // bitset(const basic_string_view<_C,_T>,
> + // size_type pos, size_type n, _C zero, _C one)
> + try {
> + std::basic_string_view<C> str(L("stuff smith sessions"));
> + const std::size_t n = 128;
> + std::bitset<n> bit(str, 5);
> + VERIFY(false);
> + }
> + catch(std::invalid_argument& fail) {
> + VERIFY( true );
> + }
> + catch(...) {
> + VERIFY( false );
> + }
> +
> + try {
> + std::basic_string_view<C> str(L("010101000011"));
> + const std::size_t n = 128;
> + const auto sz = str.size();
> + std::bitset<n> bit(str, 0);
> + std::basic_string<C> str02;
> + for (std::size_t i = 0; i < sz; ++i)
> + str02 += (bit.test(i) ? C('1') : C('0'));
> + std::reverse(str02.begin(), str02.end());
> + VERIFY( str02 == str );
> + }
> + catch(std::invalid_argument& fail) {
> + VERIFY( false );
> + }
> + catch(...) {
> + VERIFY( false );
> + }
> +
> + // substring<C> "010101000011"
> + // "10100001"
> + try {
> + std::basic_string_view<C> str(L("010101000011"));
> + const std::size_t n = 128;
> + const auto sz = 8;
> + std::bitset<n> bit(str, 3, sz);
> + std::basic_string<C> str02;
> + for (std::size_t i = 0; i < sz; ++i)
> + str02 += (bit.test(i) ? C('1') : C('0'));
> + std::reverse(str02.begin(), str02.end());
> + VERIFY( str02 == str.substr(3, sz) );
> + }
> + catch(std::invalid_argument& fail) {
> + VERIFY( false );
> + }
> + catch(...) {
> + VERIFY( false );
> + }
> +
> + // "abababaaaabb", zero = 'a', one = 'b'
> + try {
> + std::basic_string_view<C> str(L("010101000011"));
> + const std::size_t n = 128;
> + const auto sz = str.size();
> + std::basic_string<C> str02(str);
> + std::transform(str02.cbegin(), str02.cend(), str02.begin(), [](auto c) {
> + return (c - C('0')) + C('a');
> + });
> + std::basic_string_view<C> str03(str02);
> + std::bitset<n> bit(str03, 0, 100, C('a'), C('b'));
> + std::basic_string<C> str04;
> + for (std::size_t i = 0; i < sz; ++i)
> + str04 += (bit.test(i) ? C('1') : C('0'));
> + std::reverse(str04.begin(), str04.end());
> + VERIFY( str04 == str );
> + }
> + catch(std::invalid_argument& fail) {
> + VERIFY( false );
> + }
> + catch(...) {
> + VERIFY( false );
> + }
> +
> + // "aba0aba", zero = 'a', one = 'b', '0' appears
> + try {
> + const std::size_t n = 128;
> + std::basic_string_view<C> str05(L("aba0aba"));
> + std::bitset<n> bit(str05, 0, 100, C('a'), C('b'));
> + VERIFY( false );
> + }
> + catch(std::invalid_argument& fail) {
> + VERIFY( true );
> + }
> + catch(...) {
> + VERIFY( false );
> + }
> +
> + // pos > str.size()
> + try {
> + std::basic_string_view<C> str(L("010101000011"));
> + const std::size_t n = 128;
> + const auto sz = str.size();
> + std::bitset<n> bit(str, sz + 1, 100);
> + VERIFY( false );
> + }
> + catch(std::out_of_range& fail) {
> + VERIFY( true );
> + }
> + catch(...) {
> + VERIFY( false );
> + }
> +}
> +
> +int main()
> +{
> + test01();
> + return 0;
> +}
> diff --git a/libstdc++-v3/testsuite/20_util/bitset/cons/string_view_wide.cc b/libstdc++-v3/testsuite/20_util/bitset/cons/string_view_wide.cc
> new file mode 100644
> index 00000000000..b53fe74a7ce
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/bitset/cons/string_view_wide.cc
> @@ -0,0 +1,8 @@
> +// C++26 [bitset.cons]
> +
> +// { dg-do run { target c++26 } }
> +
> +#define C wchar_t
> +#define L(s) L ## s
> +
> +#include "./string_view.cc"
> --
> 2.49.0
>
More information about the Libstdc++
mailing list