On Fri, 29 Sept 2023 at 17:46, Jonathan Wakely wrote: > > On Fri, 29 Sept 2023 at 17:29, Nathaniel Shead > wrote: > > > > On Fri, Sep 29, 2023 at 04:06:33PM +0100, Jonathan Wakely wrote: > > > On Fri, 29 Sept 2023 at 10:32, Jonathan Wakely wrote: > > > > > Thanks for the comments, here's an updated version of the patch. > > > > > Bootstrapped and regtested on x86_64-pc-linux-gnu. > > > > > > > > Great, I'll get this committed today - thanks! > > > > > > That's done now. > > > > > > > Thanks! > > > > > > > > > > > > I'll note that there are some existing calls to `_M_use_local_data()` > > > > > already used only for their side effects without a cast to void, e.g. > > > > > > > > > > /** > > > > > * @brief Default constructor creates an empty string. > > > > > */ > > > > > _GLIBCXX20_CONSTEXPR > > > > > basic_string() > > > > > _GLIBCXX_NOEXCEPT_IF(is_nothrow_default_constructible<_Alloc>::value) > > > > > : _M_dataplus(_M_local_data()) > > > > > { > > > > > _M_use_local_data(); > > > > > _M_set_length(0); > > > > > } > > > > > > > > > > I haven't updated these, but should this be changed for consistency? > > > > > > > > Yes, good idea. I can do that. > > > > > > I started to do that, and decided it made more sense to split out the > > > constexpr loop from _M_use_local_data() into a separate function, > > > _M_init_local_buf(). Then we can use that for all the places where we > > > don't care about the return value. That avoids the overhead of using > > > pointer_traits::pointer_to when we don't need the return value (which > > > is admittedly only going to be an issue for -O0 code, but I think it's > > > cleaner this way anyway). > > > > > > Please see the attached patch and let me know what you think. > > > > I agree, and it also looks clearer to me what is happening. > > Good, I'll make this change next week then. > > > > > > > > > > > Thanks again for fixing these. I think this might fix some bug reports > > > > about clang rejecting our std::string in constant expressions, so I'll > > > > check those. > > > > > > Your patch fixes https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110900 > > > (so we should backport it to gcc-13 and gcc-12 too). > > > > > commit 2668979d3206ff6c039ac0165aae29377a15666c > > > Author: Jonathan Wakely > > > Date: Fri Sep 29 12:12:22 2023 > > > > > > libstdc++: Split std::basic_string::_M_use_local_data into two functions > > > > > > This splits out the activate-the-union-member-for-constexpr logic from > > > _M_use_local_data, so that it can be used separately in cases that don't > > > need to use std::pointer_traits::pointer_to to obtain the > > > return value. > > > > > > This leaves only three uses of _M_use_local_data() which are all the > > > same form: > > > > > > __s._M_data(_M_use_local_data()); > > > __s._M_set_length(0); > > > > > > We could remove _M_use_local_data() and change those three places to use > > > a new _M_reset() function that does: > > > > > > _M_init_local_buf(); > > > _M_data(_M_local_data()); > > > _M_set_length(0); > > > > > > This is left for a future change. > > > > > > libstdc++-v3/ChangeLog: > > > > > > * include/bits/basic_string.h (_M_init_local_buf()): New > > > function. > > > (_M_use_local_data()): Use _M_init_local_buf. > > > (basic_string(), basic_string(const Alloc&)) > > > (basic_string(basic_string&&)) > > > (basic_string(basic_string&&, const Alloc&)): Use > > > _M_init_local_buf instead of _M_use_local_data(). > > > * include/bits/basic_string.tcc (swap(basic_string&)) > > > (_M_construct(InIter, InIter, forward_iterator_tag)) > > > (_M_construct(size_type, CharT), reserve()): Likewise. > > > (_M_construct(InIter, InIter, input_iterator_tag)): Remove call > > > to _M_use_local_data() and initialize the local buffer directly. > > > > > > diff --git a/libstdc++-v3/include/bits/basic_string.h b/libstdc++-v3/include/bits/basic_string.h > > > index 4f94cd967cf..18a19b8dcbc 100644 > > > --- a/libstdc++-v3/include/bits/basic_string.h > > > +++ b/libstdc++-v3/include/bits/basic_string.h > > > @@ -353,13 +353,23 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > > > // Ensure that _M_local_buf is the active member of the union. > > > __attribute__((__always_inline__)) > > > _GLIBCXX14_CONSTEXPR > > > - pointer > > > - _M_use_local_data() _GLIBCXX_NOEXCEPT > > > + void > > > + _M_init_local_buf() _GLIBCXX_NOEXCEPT > > > { > > > #if __cpp_lib_is_constant_evaluated > > > if (std::is_constant_evaluated()) > > > for (size_type __i = 0; __i <= _S_local_capacity; ++__i) > > > _M_local_buf[__i] = _CharT(); > > > +#endif > > > + } > > > + > > > + __attribute__((__always_inline__)) > > > + _GLIBCXX14_CONSTEXPR > > > + pointer > > > + _M_use_local_data() _GLIBCXX_NOEXCEPT > > > + { > > > +#if __glibcxx_is_constant_evaluated > > > + _M_init_local_buf(); > > > #endif > > > return _M_local_data(); > > > } > > > > What's the difference between __cpp_lib_is_constant_evaluated and > > __glibcxx_is_constant_evaluated? Should these lines be using the same > > macro here? > > Ah, yeah, they could be the same. > > The difference is that (for most feature test macros) the > __glibcxx_ftm form is defined after including , but > the __cpp_lib_ftm form is only defined only by headers that do: > > #define __glibcxx_want_ftm > #include > > This means we can ensure that the __cpp_lib_ftm form is only defined > by headers where we actually want to define it, e.g. in the headers > that [version.syn] in the standard says should define the macro. So > for our own internal uses, we should generally rely on the > __glibcxx_ftm one. Users should rely on __cpp_lib_ftm after including > the correct header. For example, __glibcxx_atomic_wait is defined > after including and so is available for our own > uses, but __cpp_lib_atomic_wait is only defined after including > , not just . Or at least, that's the plan > - I have a pending patch to make everything I just said true :-) > Currently we "leak" the __cpp_lib_ftm forms into lots of > internal headers. That will change next week. > > In this specific case, it doesn't matter, because > __cpp_lib_is_constant_evaluated is defined by , and every > header includes that. So it doesn't really matter whether our internal > uses are __cpp_lib_is_constant_evaluated or > __glibcxx_is_constant_evaluated. But it would be good to be > consistent. > > > > > > @@ -522,7 +532,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > > > _GLIBCXX_NOEXCEPT_IF(is_nothrow_default_constructible<_Alloc>::value) > > > : _M_dataplus(_M_local_data()) > > > { > > > - _M_use_local_data(); > > > + _M_init_local_buf(); > > > _M_set_length(0); > > > } > > > > > > @@ -534,7 +544,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > > > basic_string(const _Alloc& __a) _GLIBCXX_NOEXCEPT > > > : _M_dataplus(_M_local_data(), __a) > > > { > > > - _M_use_local_data(); > > > + _M_init_local_buf(); > > > _M_set_length(0); > > > } > > > > > > @@ -678,7 +688,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > > > { > > > if (__str._M_is_local()) > > > { > > > - (void)_M_use_local_data(); > > > + _M_init_local_buf(); > > > traits_type::copy(_M_local_buf, __str._M_local_buf, > > > __str.length() + 1); > > > } > > > @@ -718,7 +728,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > > > { > > > if (__str._M_is_local()) > > > { > > > - (void)_M_use_local_data(); > > > + _M_init_local_buf(); > > > traits_type::copy(_M_local_buf, __str._M_local_buf, > > > __str.length() + 1); > > > _M_length(__str.length()); > > > diff --git a/libstdc++-v3/include/bits/basic_string.tcc b/libstdc++-v3/include/bits/basic_string.tcc > > > index 4bc98f2aea7..052eeb9e846 100644 > > > --- a/libstdc++-v3/include/bits/basic_string.tcc > > > +++ b/libstdc++-v3/include/bits/basic_string.tcc > > > @@ -79,7 +79,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > } > > > else if (__s.length()) > > > { > > > - (void)_M_use_local_data(); > > > + _M_init_local_buf(); > > > traits_type::copy(_M_local_buf, __s._M_local_buf, > > > __s.length() + 1); > > > _M_length(__s.length()); > > > @@ -88,7 +88,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > } > > > else if (length()) > > > { > > > - (void)__s._M_use_local_data(); > > > + __s._M_init_local_buf(); > > > traits_type::copy(__s._M_local_buf, _M_local_buf, > > > length() + 1); > > > __s._M_length(length()); > > > @@ -99,7 +99,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > else > > > { > > > const size_type __tmp_capacity = __s._M_allocated_capacity; > > > - (void)__s._M_use_local_data(); > > > + __s._M_init_local_buf(); > > > traits_type::copy(__s._M_local_buf, _M_local_buf, > > > length() + 1); > > > _M_data(__s._M_data()); > > > @@ -111,7 +111,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > const size_type __tmp_capacity = _M_allocated_capacity; > > > if (__s._M_is_local()) > > > { > > > - (void)_M_use_local_data(); > > > + _M_init_local_buf(); > > > traits_type::copy(_M_local_buf, __s._M_local_buf, > > > __s.length() + 1); > > > __s._M_data(_M_data()); > > > @@ -174,14 +174,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > > size_type __len = 0; > > > size_type __capacity = size_type(_S_local_capacity); > > > > > > - pointer __p = _M_use_local_data(); > > > - > > > while (__beg != __end && __len < __capacity) > > > { > > > - __p[__len++] = *__beg; > > > + _M_local_buf[__len++] = *__beg; > > > ++__beg; > > > } > > > > > > +#if __glibcxx_is_constant_evaluated > > > + if (std::is_constant_evaluated()) > > > + for (size_type __i = __len; __i <= __capacity; ++__i) > > > + _M_local_buf[__i] = _CharT(); > > > +#endif > > > + > > > > I wonder if maybe this should still be a call to `_M_init_local_buf()` > > above, where the `_M_use_local_data()` used to be? That way the logic > > stays in one place, and I don't imagine the compile time savings of not > > immediately overwriting the first __len characters would be significant. > > > Yeah, I went back and forth on that, but you're probably right. I'll > do as you suggested. Here's what I've pushed to trunk after testing on x86_64-linux. Thanks again for the fixes.