[libcxx-commits] [libcxx] [libc++] Fix std::for_each(associative-container) not using std:invoke and projections (PR #171984)

Nikolas Klauser via libcxx-commits libcxx-commits at lists.llvm.org
Mon Dec 15 01:47:34 PST 2025


https://github.com/philnik777 updated https://github.com/llvm/llvm-project/pull/171984

>From b32058c51e76a3d5d7023a8054cf5a8db87afc27 Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Fri, 12 Dec 2025 11:25:04 +0100
Subject: [PATCH] [libc++] Fix std::for_each(associative-container) not using
 std:invoke and projections

---
 libcxx/include/__tree                         |  4 +-
 ...p => ranges.for_each.associative.pass.cpp} | 73 +++++++++++++++++++
 2 files changed, 75 insertions(+), 2 deletions(-)
 rename libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/{ranges.for_each.associative.pass copy.cpp => ranges.for_each.associative.pass.cpp} (76%)

diff --git a/libcxx/include/__tree b/libcxx/include/__tree
index 22aa186470bda..2b93ea6603737 100644
--- a/libcxx/include/__tree
+++ b/libcxx/include/__tree
@@ -670,7 +670,7 @@ bool __tree_iterate_from_root(_Break __break, _NodePtr __root, _Func& __func, _P
   }
   if (__break(__root))
     return true;
-  __func(static_cast<_Reference>(__root->__get_value()));
+  std::__invoke(__func, std::__invoke(__proj, static_cast<_Reference>(__root->__get_value())));
   if (__root->__right_)
     return std::__tree_iterate_from_root<_Reference>(__break, static_cast<_NodePtr>(__root->__right_), __func, __proj);
   return false;
@@ -690,7 +690,7 @@ __tree_iterate_subrange(_NodeIter __first_it, _NodeIter __last_it, _Func& __func
     if (__first == __last)
       return;
     const auto __nfirst = static_cast<_NodePtr>(__first);
-    __func(static_cast<_Reference>(__nfirst->__get_value()));
+    std::__invoke(__func, std::__invoke(__proj, static_cast<_Reference>(__nfirst->__get_value())));
     if (__nfirst->__right_) {
       if (std::__tree_iterate_from_root<_Reference>(
               [&](_NodePtr __node) -> bool { return __node == __last; },
diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/ranges.for_each.associative.pass copy.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/ranges.for_each.associative.pass.cpp
similarity index 76%
rename from libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/ranges.for_each.associative.pass copy.cpp
rename to libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/ranges.for_each.associative.pass.cpp
index b78adcc461ed1..11370069612e7 100644
--- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/ranges.for_each.associative.pass copy.cpp	
+++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/ranges.for_each.associative.pass.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 // <algorithm>
+// UNSUPPORTED: c++03, c++11, c++14, c++17
 
 // Check that the special implementation of ranges::for_each for the associative container iterators works as expected
 
@@ -158,11 +159,83 @@ void test_node_container(Converter conv) {
   }
 }
 
+template <template <class> class Container>
+void test_invoke_set_like() {
+
+  { // check that std::invoke is used
+    struct T {
+      mutable int i = 3;
+
+      void zero() const { i = 0; }
+    };
+
+    class S {
+      int val_;
+
+    public:
+      S(int val) : val_(val) {}
+
+      T j;
+
+      bool operator<(const S& rhs) const { return val_ < rhs.val_; }
+    };
+
+    { // Iterator overload
+      Container<S> a = {S{2}, S{4}, S{6}};
+      std::ranges::for_each(a.begin(), a.end(), &T::zero, &S::j);
+      assert(a.find(2)->j.i == 0);
+      assert(a.find(4)->j.i == 0);
+      assert(a.find(6)->j.i == 0);
+    }
+    { // Range overload
+      Container<S> a = {S{2}, S{4}, S{6}};
+      std::ranges::for_each(a, &T::zero, &S::j);
+      assert(a.find(2)->j.i == 0);
+      assert(a.find(4)->j.i == 0);
+      assert(a.find(6)->j.i == 0);
+    }
+  }
+}
+
+template <template <class, class> class Container>
+void test_invoke_map_like() {
+
+  { // check that std::invoke is used
+    struct S {
+      int i;
+
+      void zero() { i = 0; }
+    };
+
+    { // Iterator overload
+      Container<int, S> a = {{1, S{2}}, {3, S{4}}, {5, S{6}}};
+      std::ranges::for_each(a.begin(), a.end(), &S::zero, &std::pair<const int, S>::second);
+      assert(a.find(1)->second.i == 0);
+      assert(a.find(3)->second.i == 0);
+      assert(a.find(5)->second.i == 0);
+    }
+    { // Range overload
+      Container<int, S> a = {{1, S{2}}, {3, S{4}}, {5, S{6}}};
+      std::ranges::for_each(a, &S::zero, &std::pair<const int, S>::second);
+      assert(a.find(1)->second.i == 0);
+      assert(a.find(3)->second.i == 0);
+      assert(a.find(5)->second.i == 0);
+    }
+  }
+}
+
 int main(int, char**) {
   test_node_container<std::set<int> >([](int i) { return i; });
   test_node_container<std::multiset<int> >([](int i) { return i; });
   test_node_container<std::map<int, int> >([](int i) { return std::make_pair(i, i); });
   test_node_container<std::multimap<int, int> >([](int i) { return std::make_pair(i, i); });
 
+  test_invoke_set_like<std::set>();
+  test_invoke_set_like<std::multiset>();
+
+  test_invoke_map_like<std::map>();
+  test_invoke_map_like<std::multimap>();
+
+
   return 0;
 }



More information about the libcxx-commits mailing list