On Mon, 13 Jul 2026, Tomasz Kaminski wrote:

> 
> 
> On Mon, Jul 13, 2026 at 5:22 PM Patrick Palka <[email protected]> wrote:
>       Tested on x86_64-pc-linux-gnu, does this look OK for trunk and perhaps 
> 16?
>       I'm not sure if it's worth avoiding dereferencing *__first twice in this
>       special case, but I figured we might as well?
> 
>       -- >8 --
> 
>       When inserting a range of pair-like elements we can avoid constructing a
>       value_type temporary and instead obtain the corresponding key and value
>       to insert directly from *__first.
> 
>       libstdc++-v3/ChangeLog:
> 
>               * include/std/flat_map (flat_map::_M_insert): Avoid constructing
>               value_type temporary when the iterator already has pair-like
>               elements.
>       ---
>        libstdc++-v3/include/std/flat_map | 17 ++++++++++++++---
>        1 file changed, 14 insertions(+), 3 deletions(-)
> 
> LGTM, only small suggestions. 
> 
>       diff --git a/libstdc++-v3/include/std/flat_map 
> b/libstdc++-v3/include/std/flat_map
>       index b82d41b4f5e1..1815ced51225 100644
>       --- a/libstdc++-v3/include/std/flat_map
>       +++ b/libstdc++-v3/include/std/flat_map
>       @@ -637,9 +637,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>                 auto __n = size();
>                 for (; __first != __last; ++__first)
>                   {
> 
> I do not think we need this braces for nesting. 

Fixed.

>       -             value_type __value = *__first;
>       -             _M_cont.keys.emplace_back(std::move(__value.first));
>       -             _M_cont.values.emplace_back(std::move(__value.second));
>       +             if constexpr (__pair_like<iter_reference_t<_Iter>>)
>       +               {
>       +                 auto&& __value = *__first;
>       +                 _M_cont.keys.emplace_back
>       +                   
> (std::get<0>(std::forward<decltype(__value)>(__value)));
>       +                 _M_cont.values.emplace_back
>       +                   
> (std::get<1>(std::forward<decltype(__value)>(__value)));
> 
> // Maybe we could alias iter_reference_t<_Iter> and replace the 
> decltypes(__value) and auto&&? 

Sounds good. Avoiding temporary materialization and an intermediate
reference should be cheaper with -O0.

>       +               }
>       +             else
>       +               {
>       +                 value_type __value = *__first;
>       +                 _M_cont.keys.emplace_back(std::move(__value.first));
>       +                 
> _M_cont.values.emplace_back(std::move(__value.second));
>       +               }
>                   }
>                 auto __zv = views::zip(_M_cont.keys, _M_cont.values);
>                 if (__is_sorted)
>       --
>       2.55.0.122.gf85a7e6620
> 
> Do you think it will be worth to have a test that will count number of moves, 
> when
> we pass sourted_unique flag is passed? 

Sure. Like so?

Changes in v2:
  - Addresses Tomasz's comments
  - Clarified relevance of LWG 4499

-- >8 --

Subject: [PATCH v2] libstdc++: Optimize flat_map range insertion for pair-like
 elements

When inserting a range of pair-like elements we can avoid constructing a
value_type temporary and instead obtain the corresponding key and value
directly from *__first.

This came up when looking at LWG 4499 for flat_set::insert_range (which
I think we already implement, with no extra std::move) but it prompted
me to look at flat_map::insert_range during which I noticed this extra
std::move.

libstdc++-v3/ChangeLog:

        * include/std/flat_map (flat_map::_M_insert): Avoid constructing
        value_type temporary when the iterator already has pair-like
        elements.
        * testsuite/23_containers/flat_map/1.cc (test14): New test.
---
 libstdc++-v3/include/std/flat_map             | 18 +++++++---
 .../testsuite/23_containers/flat_map/1.cc     | 34 +++++++++++++++++++
 2 files changed, 47 insertions(+), 5 deletions(-)

diff --git a/libstdc++-v3/include/std/flat_map 
b/libstdc++-v3/include/std/flat_map
index b82d41b4f5e1..ad5fddfe29e1 100644
--- a/libstdc++-v3/include/std/flat_map
+++ b/libstdc++-v3/include/std/flat_map
@@ -635,12 +635,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        {
          auto __guard = _M_make_clear_guard();
          auto __n = size();
+         using __ref = iter_reference_t<_Iter>;
          for (; __first != __last; ++__first)
-           {
-             value_type __value = *__first;
-             _M_cont.keys.emplace_back(std::move(__value.first));
-             _M_cont.values.emplace_back(std::move(__value.second));
-           }
+           if constexpr (__pair_like<__ref>)
+             {
+               __ref __value = *__first;
+               
_M_cont.keys.emplace_back(std::get<0>(std::forward<__ref>(__value)));
+               
_M_cont.values.emplace_back(std::get<1>(std::forward<__ref>(__value)));
+             }
+           else
+             {
+               value_type __value = *__first;
+               _M_cont.keys.emplace_back(std::move(__value.first));
+               _M_cont.values.emplace_back(std::move(__value.second));
+             }
          auto __zv = views::zip(_M_cont.keys, _M_cont.values);
          if (__is_sorted)
            _GLIBCXX_DEBUG_ASSERT(ranges::is_sorted(__zv.begin() + __n, 
__zv.end(),
diff --git a/libstdc++-v3/testsuite/23_containers/flat_map/1.cc 
b/libstdc++-v3/testsuite/23_containers/flat_map/1.cc
index 0cd06b72e96c..0e539af3e5f2 100644
--- a/libstdc++-v3/testsuite/23_containers/flat_map/1.cc
+++ b/libstdc++-v3/testsuite/23_containers/flat_map/1.cc
@@ -405,6 +405,38 @@ test13()
   VERIFY( std::ranges::equal(s.values(), (int[]){3, 1, 2}) );
 }
 
+void
+test14()
+{
+  // Verify optimal number of moves in flat_map::insert_range for sorted_unique
+  static int moves;
+  struct counter
+  {
+    int val;
+    constexpr counter() = default;
+    constexpr counter(int v) : val(v) {}
+    constexpr counter(const counter&) = default;
+    constexpr counter(counter&& o) noexcept : val(o.val) { ++moves; }
+    constexpr counter& operator=(const counter& o) = default;
+    constexpr counter& operator=(counter&& o) noexcept {
+      val = o.val;
+      ++moves;
+      return *this;
+    }
+    constexpr bool operator==(const counter&) const = default;
+    constexpr auto operator<=>(const counter& o) const = default;
+  };
+
+  std::flat_map<counter, counter> m;
+  std::pair<counter, counter> s[] = {
+    {counter(1), counter(10)},
+    {counter(2), counter(20)}
+  };
+  moves = 0;
+  m.insert_range(std::sorted_unique, std::views::as_rvalue(s));
+  VERIFY( moves == 4 );
+}
+
 void
 test()
 {
@@ -425,6 +457,7 @@ test()
   test11<throwing_vector, std::vector>();
   test12();
   test13();
+  test14();
 }
 
 constexpr
@@ -446,6 +479,7 @@ test_constexpr()
   // test11() is non-constexpr
   test12();
   // test13() is non-constexpr
+  // test14() is non-constexpr
   return true;
 }
 
-- 
2.55.0.178.gd35c5399e3

Reply via email to