On Fri, Apr 4, 2025 at 6:07 PM Tomasz Kaminski <tkami...@redhat.com> wrote:

>
>
> On Fri, Apr 4, 2025 at 5:50 PM Patrick Palka <ppa...@redhat.com> wrote:
>
>> flat_set::emplace (and flat_multiset) currently unconditionally
>> constructs an object outside of the container, but if we're passed
>> a value_type object we can and should avoid that.
>>
>>         PR libstdc++/119620
>>
>> libstdc++-v3/ChangeLog:
>>
>>         * include/std/flat_set (_Flat_set_impl::_M_try_emplace): Split
>>         out into two overloads, one taking at least one argument and one
>>         taking zero arguments.  Turn __k into an auto&& reference bound
>>         to __arg if it's already a value_type otherwise bound to a
>>         lifetime-extended value_type temporary.
>>         * testsuite/23_containers/flat_multiset/1.cc (test08): New test.
>>         * testsuite/23_containers/flat_set/1.cc (test08): New test.
>> ---
>>  libstdc++-v3/include/std/flat_set             | 20 +++++++++++++----
>>  .../23_containers/flat_multiset/1.cc          | 22 +++++++++++++++++++
>>  .../testsuite/23_containers/flat_set/1.cc     | 20 +++++++++++++++++
>>  3 files changed, 58 insertions(+), 4 deletions(-)
>>
>> diff --git a/libstdc++-v3/include/std/flat_set
>> b/libstdc++-v3/include/std/flat_set
>> index a7b0b8aef15..3e15d1af416 100644
>> --- a/libstdc++-v3/include/std/flat_set
>> +++ b/libstdc++-v3/include/std/flat_set
>> @@ -350,12 +350,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>        { return _M_cont.max_size(); }
>>
>>        // modifiers
>> -      template<typename... _Args>
>> +      template<typename _Arg, typename... _Args>
>>         pair<iterator, bool>
>> -       _M_try_emplace(optional<const_iterator> __hint, _Args&&... __args)
>> +       _M_try_emplace(optional<const_iterator> __hint, _Arg&& __arg,
>> _Args&&... __args)
>>
> map.try_emplace(key) is valid construct, and commonly used to value
> construct value type. For example operator[] is defined in terms of it.
>
>>         {
>>           // TODO: Simplify and audit the hint handling.
>> -         value_type __k(std::forward<_Args>(__args)...);
>> +         auto&& __k = [&] -> decltype(auto) {
>> +           if constexpr (sizeof...(_Args) == 0
>> +                         && same_as<remove_cvref_t<_Arg>, value_type>)
>> +             return std::forward<_Arg>(__arg);
>>
> I think you can do sizeof..(_Args) == 1 and return
> (std::forward<_Args>(__args), ...);
>
>> +           else
>> +             return value_type(std::forward<_Arg>(__arg),
>> +                               std::forward<_Args>(__args)...);
>> +         }();
>>           typename container_type::iterator __it;
>>           int __r = -1, __s = -1;
>>           if (__hint.has_value()
>> @@ -397,11 +404,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>               return {__it, false};
>>
>>           auto __guard = _M_make_clear_guard();
>> -         __it = _M_cont.insert(__it, std::move(__k));
>> +         __it = _M_cont.insert(__it, std::forward<decltype(__k)>(__k));
>>           __guard._M_disable();
>>           return {__it, true};
>>         }
>>
>> +      template<typename... _Args>
>> +       pair<iterator, bool>
>> +       _M_try_emplace(optional<const_iterator> __hint)
>> +       { return _M_try_emplace(__hint, value_type()); }
>>
> Ah, I haven't noticed this overload that restores _M_try_empalce. But this
causes additional move, so I think I prefer my suggestion.

> +
>>        template<typename... _Args>
>>         requires is_constructible_v<value_type, _Args...>
>>         __emplace_result_t
>> diff --git a/libstdc++-v3/testsuite/23_containers/flat_multiset/1.cc
>> b/libstdc++-v3/testsuite/23_containers/flat_multiset/1.cc
>> index dc3cecd9720..7d9a33c6b00 100644
>> --- a/libstdc++-v3/testsuite/23_containers/flat_multiset/1.cc
>> +++ b/libstdc++-v3/testsuite/23_containers/flat_multiset/1.cc
>> @@ -214,6 +214,27 @@ void test07()
>>  #endif
>>  }
>>
>> +void
>> +test08()
>> +{
>> +  // PR libstdc++/119620 -- flat_set::emplace always constructs element
>> on the stack
>> +  static int copy_counter;
>> +  struct A {
>> +    A() { }
>> +    A(const A&) { ++copy_counter; }
>> +    A& operator=(const A&) { ++copy_counter; return *this; }
>> +    auto operator<=>(const A&) const = default;
>> +  };
>> +  std::vector<A> v;
>> +  v.reserve(2);
>> +  std::flat_multiset<A> s(std::move(v));
>> +  A a;
>> +  s.emplace(a);
>> +  VERIFY( copy_counter == 1 );
>> +  s.emplace(a);
>> +  VERIFY( copy_counter == 2 );
>> +}
>> +
>>  int
>>  main()
>>  {
>> @@ -225,4 +246,5 @@ main()
>>    test05();
>>    test06();
>>    test07();
>> +  test08();
>>  }
>> diff --git a/libstdc++-v3/testsuite/23_containers/flat_set/1.cc
>> b/libstdc++-v3/testsuite/23_containers/flat_set/1.cc
>> index 90f5855859f..ed24fab785b 100644
>> --- a/libstdc++-v3/testsuite/23_containers/flat_set/1.cc
>> +++ b/libstdc++-v3/testsuite/23_containers/flat_set/1.cc
>> @@ -229,6 +229,25 @@ void test07()
>>  #endif
>>  }
>>
>> +void
>> +test08()
>> +{
>> +  // PR libstdc++/119620 -- flat_set::emplace always constructs element
>> on the stack
>> +  static int copy_counter;
>> +  struct A {
>> +    A() { }
>> +    A(const A&) { ++copy_counter; }
>> +    A& operator=(const A&) { ++copy_counter; return *this; }
>> +    auto operator<=>(const A&) const = default;
>> +  };
>> +  std::flat_set<A> s;
>> +  A a;
>> +  s.emplace(a);
>> +  VERIFY( copy_counter == 1 );
>> +  s.emplace(a);
>> +  VERIFY( copy_counter == 1 );
>> +}
>> +
>>  int
>>  main()
>>  {
>> @@ -240,4 +259,5 @@ main()
>>    test05();
>>    test06();
>>    test07();
>> +  test08();
>>  }
>> --
>> 2.49.0.111.g5b97a56fa0
>>
>>

Reply via email to