On Mon, Apr 14, 2025 at 6:08 PM Jonathan Wakely <jwak...@redhat.com> wrote:
> On Mon, 14 Apr 2025 at 16:25, Tomasz Kaminski <tkami...@redhat.com> wrote: > > > > > > > > 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. > > Ah yes, I forgot to fix that part of the commit message. > > > 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. > > I agree we still need to create a temporary, but there's no deduction > involved. traits_type::assign takes charT& and const charT&, and the > volatile charT& cannot bind to the latter. > > I'll change the second paragraph of the commit message to say: > > Additionally, some generic loops for non-contiguous iterators need an > explicit cast to deal with iterator reference types that do not bind to > the const charT& parameter of traits_type::assign. > > > > >> 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*>>) > > Yes, in general I would prefer that, but then the else-branch would > bind incorrectly: > > if constexpr (pointer or string::iterator) > { } > else if constexpr (contiguous_range<R>) > if constexpr (convertible to pointer) > { } > else > for-loop > > And it can't be a single condition because the ranges::data call isn't > valid unless it's a contiguous iterator. > > I could write it: > > if constexpr (pointer or string::iterator) > { } > else if constexpr (contiguous_range<R>) > if constexpr (convertible to pointer) > { } > else > for-loop > else > for-loop > > Or just don't use a discarded statement for the for-loop: > > if constexpr (pointer or string::iterator) > { } > else if constexpr (contiguous_range<R>) > if constexpr (convertible to pointer) > { } > for-loop > > But neither of these seems clearly better than just using a single > constexpr-if with a requires-expression as the condition. > Indeed, having a single if requires expression seems like the best option in this situation. > > > >> > >> { > >> 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? > > I thought they didn't work with volatile pointers, but it looks like I > was wrong. > >