On Mon, Apr 14, 2025 at 5:06 PM Jonathan Wakely <jwak...@redhat.com> wrote:
> My recent r15-9381-g648d5c26e25497 change assumes that a contiguous > iterator with the correct value_type can be converted to a const charT* > but that's not true for volatile charT*. The optimization should only be > done if it can be converted to the right pointer type. > > Additionally, the generic loop for non-contiguous iterators needs an > explicit cast to deal with iterators that have a reference type that is > not explicitly convertible to charT. > As mentioned before, we do not need to handle explicit conversion for CharT. We still need to perform static_cast to create a temporary CharT when passing to traits::assign, as otherwise we will get a mismatch in deduction. > > libstdc++-v3/ChangeLog: > > PR libstdc++/119748 > * include/bits/basic_string.h (_S_copy_chars): Only optimize for > contiguous iterators that are convertible to const charT*. Use > explicit conversion to charT after dereferencing iterator. > (_S_copy_range): Likewise for contiguous ranges. > * include/bits/basic_string.tcc (_M_construct): Use explicit > conversion to charT after dereferencing input iterator. > * include/bits/cow_string.h (_S_copy_chars): Likewise. > (basic_string(from_range_t, R&&, const Allocator&)): Likewise. > Only optimize for contiguous iterators that are convertible to > const charT*. > * testsuite/21_strings/basic_string/cons/char/119748.cc: New > test. > * testsuite/21_strings/basic_string/cons/wchar_t/119748.cc: > New test. > --- > > Changes in v2: > - Added static_assert in _M_construct overload for input iterators. > - Added tests using non-contiguous iterators. > > libstdc++-v3/include/bits/basic_string.h | 24 +++++--- > libstdc++-v3/include/bits/basic_string.tcc | 3 +- > libstdc++-v3/include/bits/cow_string.h | 17 ++++-- > .../basic_string/cons/char/119748.cc | 55 +++++++++++++++++++ > .../basic_string/cons/wchar_t/119748.cc | 7 +++ > 5 files changed, 93 insertions(+), 13 deletions(-) > create mode 100644 > libstdc++-v3/testsuite/21_strings/basic_string/cons/char/119748.cc > create mode 100644 > libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/119748.cc > > diff --git a/libstdc++-v3/include/bits/basic_string.h > b/libstdc++-v3/include/bits/basic_string.h > index 9c431c765ab..c90bd099b63 100644 > --- a/libstdc++-v3/include/bits/basic_string.h > +++ b/libstdc++-v3/include/bits/basic_string.h > @@ -488,8 +488,11 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > is_same<_IterBase, const _CharT*>>::value) > _S_copy(__p, std::__niter_base(__k1), __k2 - __k1); > #if __cpp_lib_concepts > - else if constexpr (contiguous_iterator<_Iterator> > - && is_same_v<iter_value_t<_Iterator>, > _CharT>) > + else if constexpr (requires { > + requires contiguous_iterator<_Iterator>; > + { std::to_address(__k1) } > + -> convertible_to<const _CharT*>; > + }) > I would prefer, but this is not a strong preference. if constexpr (ranges::contiguous_range<_Rg>) if constexpr (convertible_to<decltype(ranges::data(std::forward<_Rg>(__rg))), const CharT*>>) > { > const auto __d = __k2 - __k1; > (void) (__k1 + __d); // See P3349R1 > @@ -499,7 +502,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > else > #endif > for (; __k1 != __k2; ++__k1, (void)++__p) > - traits_type::assign(*__p, *__k1); // These types are off. > + traits_type::assign(*__p, static_cast<_CharT>(*__k1)); > } > #pragma GCC diagnostic pop > > @@ -527,12 +530,19 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > static constexpr void > _S_copy_range(pointer __p, _Rg&& __rg, size_type __n) > { > - if constexpr (ranges::contiguous_range<_Rg> > - && is_same_v<ranges::range_value_t<_Rg>, _CharT>) > + if constexpr (requires { > + requires ranges::contiguous_range<_Rg>; > + { ranges::data(std::forward<_Rg>(__rg)) } > + -> convertible_to<const _CharT*>; > Same as before. + }) > _S_copy(__p, ranges::data(std::forward<_Rg>(__rg)), __n); > else > - for (auto&& __e : __rg) > - traits_type::assign(*__p++, > std::forward<decltype(__e)>(__e)); > + { > + auto __first = ranges::begin(__rg); > + const auto __last = ranges::end(__rg); > + for (; __first != __last; ++__first) > + traits_type::assign(*__p++, static_cast<_CharT>(*__first)); > + } > } > #endif > > diff --git a/libstdc++-v3/include/bits/basic_string.tcc > b/libstdc++-v3/include/bits/basic_string.tcc > index 02230aca5d2..bca55bc5658 100644 > --- a/libstdc++-v3/include/bits/basic_string.tcc > +++ b/libstdc++-v3/include/bits/basic_string.tcc > @@ -210,7 +210,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > _M_data(__another); > _M_capacity(__capacity); > } > - traits_type::assign(_M_data()[__len++], *__beg); > + traits_type::assign(_M_data()[__len++], > + static_cast<_CharT>(*__beg)); > ++__beg; > } > > diff --git a/libstdc++-v3/include/bits/cow_string.h > b/libstdc++-v3/include/bits/cow_string.h > index b250397151b..f9df2be20be 100644 > --- a/libstdc++-v3/include/bits/cow_string.h > +++ b/libstdc++-v3/include/bits/cow_string.h > @@ -423,7 +423,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > _S_copy_chars(_CharT* __p, _Iterator __k1, _Iterator __k2) > { > for (; __k1 != __k2; ++__k1, (void)++__p) > - traits_type::assign(*__p, *__k1); // These types are off. > + traits_type::assign(*__p, static_cast<_CharT>(*__k1)); > } > > static void > @@ -656,12 +656,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > reserve(__n); > pointer __p = _M_data(); > - if constexpr (ranges::contiguous_range<_Rg> > - && is_same_v<ranges::range_value_t<_Rg>, > _CharT>) > + if constexpr (requires { > + requires ranges::contiguous_range<_Rg>; > + { ranges::data(std::forward<_Rg>(__rg)) } > + -> convertible_to<const _CharT*>; > + }) > _M_copy(__p, ranges::data(std::forward<_Rg>(__rg)), __n); > else > - for (auto&& __e : __rg) > - traits_type::assign(*__p++, > std::forward<decltype(__e)>(__e)); > + { > + auto __first = ranges::begin(__rg); > + const auto __last = ranges::end(__rg); > + for (; __first != __last; ++__first) > + traits_type::assign(*__p++, > static_cast<_CharT>(*__first)); > + } > _M_rep()->_M_set_length_and_sharable(__n); > } > else > diff --git > a/libstdc++-v3/testsuite/21_strings/basic_string/cons/char/119748.cc > b/libstdc++-v3/testsuite/21_strings/basic_string/cons/char/119748.cc > new file mode 100644 > index 00000000000..c8be6f4ec0a > --- /dev/null > +++ b/libstdc++-v3/testsuite/21_strings/basic_string/cons/char/119748.cc > @@ -0,0 +1,55 @@ > +// { dg-do compile } > + > +// Bug 119748 > +// string(InputIterator, InputIterator) rejects volatile charT* as > iterator > + > +#ifndef TEST_CHAR_TYPE > +#define TEST_CHAR_TYPE char > +#endif > + > +#include <string> > + > +typedef TEST_CHAR_TYPE C; > + > +volatile C vs[42] = {}; > +std::basic_string<C> s(vs+0, vs+42); > +#ifdef __cpp_lib_containers_ranges > +std::basic_string<C> s2(std::from_range, vs); > +#endif > + > +template<class Cat> > +struct Iterator > +{ > + typedef C value_type; > + typedef volatile C& reference; > + typedef volatile C* pointer; > + typedef long difference_type; > + typedef Cat iterator_category; > + > + reference operator*() const { return const_cast<reference>(*c); }; > + > + Iterator& operator++() { ++c; return *this; } > + Iterator operator++(int) { Iterator i = { c++ }; return i; } > + > + bool operator==(const Iterator& i) const { return c == i.c; } > + bool operator!=(const Iterator& i) const { return !(*this == i); } > + > + C* c; > +}; > + > +typedef Iterator<std::input_iterator_tag> InputIterator; > Any reason for creating own iterators, instead relying on one from test_iterators.h? > +C chars[] = { C('a'), C('b'), C('c') }; > +InputIterator first = { chars + 0 }; > +InputIterator last = { chars + 3 }; > +std::basic_string<C> s3(first, last); > +#ifdef __cpp_lib_containers_ranges > +std::basic_string<C> s4(std::from_range, std::ranges::subrange(first, > last)); > +#endif > + > +typedef Iterator<std::forward_iterator_tag> ForwardIterator; > +ForwardIterator firstf = { chars+0 }; > +ForwardIterator lastf = { chars + 3 }; > +std::basic_string<C> s5(firstf, lastf); > +#ifdef __cpp_lib_containers_ranges > +std::basic_string<C> s6(std::from_range, std::ranges::subrange(firstf, > lastf)); > +#endif > diff --git > a/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/119748.cc > b/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/119748.cc > new file mode 100644 > index 00000000000..7d3ba10d2d4 > --- /dev/null > +++ b/libstdc++-v3/testsuite/21_strings/basic_string/cons/wchar_t/119748.cc > @@ -0,0 +1,7 @@ > +// { dg-do compile } > + > +// Bug 119748 > +// string(InputIterator, InputIterator) rejects volatile charT* as > iterator > + > +#define TEST_CHAR_TYPE wchar_t > +#include "../char/119748.cc" > -- > 2.49.0 > >