On Mon, 14 Apr 2025 at 16:25, Tomasz Kaminski <[email protected]> wrote:
>
>
>
> On Mon, Apr 14, 2025 at 5:06 PM Jonathan Wakely <[email protected]> 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.
>>
>> {
>> 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.