On Fri, Apr 4, 2025 at 6:21 PM Patrick Palka <ppa...@redhat.com> wrote:

> On Fri, 4 Apr 2025, Patrick Palka wrote:
>
> > On Fri, 4 Apr 2025, Tomasz Kaminski wrote:
> >
> > >
> > >
> > > 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), ...);
>
> I tried something like that, but I think it'd mean we'd have to
> then change the same_as check to
>
>   same_as<remove_cvref_t<_Args...>, value_type>
>
> or
>
>   same_as<remove_cvref_t<_Args>..., value_type>
>
> both of which are ill-formed because we can't pass a pack expansion
> to a non-variadic template parameter of an alias template or concept.
>
Thanks for the explanation. LGTM.

>
> > >       +           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.
> >
> > I'd expect there would be just one move in that case, when __k is moved
> > from in the _M_cont.insert call?  There should be no move when we e.g.
> > bind __k to the default-constructed value_type.
>
Yes, indeed, we are binding to `auto&&`, not to value_type. I am ok with
the approach.


> >
> > >             +
> > >                    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