Skip to content

Conversation

kazutakahirata
Copy link
Contributor

StringMap has four iterator classes:

  • StringMapIterBase
  • StringMapIterator
  • StringMapConstIterator
  • StringMapKeyIterator

This patch consolidates the first three into one class, namely
StringMapIterBase, adds a boolean template parameter to indicate
desired constness, and then use "using" directives to specialize the
common class:

using const_iterator = StringMapIterBase<ValueTy, true>;
using iterator = StringMapIterBase<ValueTy, false>;

just like how we simplified DenseMapIterator.

Remarks:

  • This patch drops CRTP and iterator_facade_base for simplicity. For
    fairly simple forward iterators, iterator_facade_base doesn't buy us
    much. We just have to write a few "using" directives and operator!=
    manually.

  • StringMapIterBase has a SFINAE-based constructor to construct a
    const iterator from a non-const one just like DenseMapIterator.

  • We now rely on compiler-generated copy and assignment operators.

StringMap has four iterator classes:

- StringMapIterBase
- StringMapIterator
- StringMapConstIterator
- StringMapKeyIterator

This patch consolidates the first three into one class, namely
StringMapIterBase, adds a boolean template parameter to indicate
desired constness, and then use "using" directives to specialize the
common class:

  using const_iterator = StringMapIterBase<ValueTy, true>;
  using iterator = StringMapIterBase<ValueTy, false>;

just like how we simplified DenseMapIterator.

Remarks:

- This patch drops CRTP and iterator_facade_base for simplicity.  For
  fairly simple forward iterators, iterator_facade_base doesn't buy us
  much.  We just have to write a few "using" directives and operator!=
  manually.

- StringMapIterBase has a SFINAE-based constructor to construct a
  const iterator from a non-const one just like DenseMapIterator.

- We now rely on compiler-generated copy and assignment operators.
@llvmbot llvmbot added clang Clang issues not falling into any other category clang-tools-extra clang-tidy clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' llvm:adt labels Aug 30, 2025
@llvmbot
Copy link
Member

llvmbot commented Aug 30, 2025

@llvm/pr-subscribers-clang
@llvm/pr-subscribers-clang-tidy

@llvm/pr-subscribers-clang-tools-extra

Author: Kazu Hirata (kazutakahirata)

Changes

StringMap has four iterator classes:

  • StringMapIterBase
  • StringMapIterator
  • StringMapConstIterator
  • StringMapKeyIterator

This patch consolidates the first three into one class, namely
StringMapIterBase, adds a boolean template parameter to indicate
desired constness, and then use "using" directives to specialize the
common class:

using const_iterator = StringMapIterBase<ValueTy, true>;
using iterator = StringMapIterBase<ValueTy, false>;

just like how we simplified DenseMapIterator.

Remarks:

  • This patch drops CRTP and iterator_facade_base for simplicity. For
    fairly simple forward iterators, iterator_facade_base doesn't buy us
    much. We just have to write a few "using" directives and operator!=
    manually.

  • StringMapIterBase has a SFINAE-based constructor to construct a
    const iterator from a non-const one just like DenseMapIterator.

  • We now rely on compiler-generated copy and assignment operators.


Full diff: https://github.com/llvm/llvm-project/pull/156137.diff

3 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp (+1-1)
  • (modified) clang/lib/Driver/OffloadBundler.cpp (+1-2)
  • (modified) llvm/include/llvm/ADT/StringMap.h (+37-61)
diff --git a/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp b/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp
index 36b6007b58a51..4382f9df5336e 100644
--- a/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp
+++ b/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp
@@ -39,7 +39,7 @@ bool MultipleInheritanceCheck::getInterfaceStatus(const CXXRecordDecl *Node,
                                                   bool &IsInterface) const {
   assert(Node->getIdentifier());
   StringRef Name = Node->getIdentifier()->getName();
-  llvm::StringMapConstIterator<bool> Pair = InterfaceMap.find(Name);
+  auto Pair = InterfaceMap.find(Name);
   if (Pair == InterfaceMap.end())
     return false;
   IsInterface = Pair->second;
diff --git a/clang/lib/Driver/OffloadBundler.cpp b/clang/lib/Driver/OffloadBundler.cpp
index e83aee01b75cb..f69ac41dddb3e 100644
--- a/clang/lib/Driver/OffloadBundler.cpp
+++ b/clang/lib/Driver/OffloadBundler.cpp
@@ -1936,8 +1936,7 @@ Error OffloadBundler::UnbundleArchive() {
   /// Write out an archive for each target
   for (auto &Target : BundlerConfig.TargetNames) {
     StringRef FileName = TargetOutputFileNameMap[Target];
-    StringMapIterator<std::vector<llvm::NewArchiveMember>> CurArchiveMembers =
-        OutputArchivesMap.find(Target);
+    auto CurArchiveMembers = OutputArchivesMap.find(Target);
     if (CurArchiveMembers != OutputArchivesMap.end()) {
       if (Error WriteErr = writeArchive(FileName, CurArchiveMembers->getValue(),
                                         SymtabWritingMode::NormalSymtab,
diff --git a/llvm/include/llvm/ADT/StringMap.h b/llvm/include/llvm/ADT/StringMap.h
index dbc0485967ac6..aabe8cd7903c8 100644
--- a/llvm/include/llvm/ADT/StringMap.h
+++ b/llvm/include/llvm/ADT/StringMap.h
@@ -21,11 +21,11 @@
 #include "llvm/Support/PointerLikeTypeTraits.h"
 #include <initializer_list>
 #include <iterator>
+#include <type_traits>
 
 namespace llvm {
 
-template <typename ValueTy> class StringMapConstIterator;
-template <typename ValueTy> class StringMapIterator;
+template <typename ValueTy, bool IsConst> class StringMapIterBase;
 template <typename ValueTy> class StringMapKeyIterator;
 
 /// StringMapImpl - This is the base class of StringMap that is shared among
@@ -217,8 +217,8 @@ class LLVM_ALLOCATORHOLDER_EMPTYBASE StringMap
   using value_type = StringMapEntry<ValueTy>;
   using size_type = size_t;
 
-  using const_iterator = StringMapConstIterator<ValueTy>;
-  using iterator = StringMapIterator<ValueTy>;
+  using const_iterator = StringMapIterBase<ValueTy, true>;
+  using iterator = StringMapIterBase<ValueTy, false>;
 
   iterator begin() { return iterator(TheTable, NumBuckets == 0); }
   iterator end() { return iterator(TheTable + NumBuckets, true); }
@@ -431,14 +431,19 @@ class LLVM_ALLOCATORHOLDER_EMPTYBASE StringMap
   }
 };
 
-template <typename DerivedTy, typename ValueTy>
-class StringMapIterBase
-    : public iterator_facade_base<DerivedTy, std::forward_iterator_tag,
-                                  ValueTy> {
-protected:
+template <typename ValueTy, bool IsConst> class StringMapIterBase {
+  using EntryTy = std::conditional_t<IsConst, const StringMapEntry<ValueTy>,
+                                     StringMapEntry<ValueTy>>;
+
   StringMapEntryBase **Ptr = nullptr;
 
 public:
+  using iterator_category = std::forward_iterator_tag;
+  using value_type = EntryTy;
+  using difference_type = std::ptrdiff_t;
+  using pointer = value_type *;
+  using reference = value_type &;
+
   StringMapIterBase() = default;
 
   explicit StringMapIterBase(StringMapEntryBase **Bucket,
@@ -448,85 +453,56 @@ class StringMapIterBase
       AdvancePastEmptyBuckets();
   }
 
-  DerivedTy &operator=(const DerivedTy &Other) {
-    Ptr = Other.Ptr;
-    return static_cast<DerivedTy &>(*this);
-  }
-
-  friend bool operator==(const DerivedTy &LHS, const DerivedTy &RHS) {
-    return LHS.Ptr == RHS.Ptr;
-  }
+  reference operator*() const { return *static_cast<EntryTy *>(*Ptr); }
+  pointer operator->() const { return &operator*(); }
 
-  DerivedTy &operator++() { // Preincrement
+  StringMapIterBase &operator++() { // Preincrement
     ++Ptr;
     AdvancePastEmptyBuckets();
-    return static_cast<DerivedTy &>(*this);
+    return *this;
   }
 
-  DerivedTy operator++(int) { // Post-increment
-    DerivedTy Tmp(Ptr);
+  StringMapIterBase operator++(int) { // Post-increment
+    StringMapIterBase Tmp(*this);
     ++*this;
     return Tmp;
   }
 
-private:
-  void AdvancePastEmptyBuckets() {
-    while (*Ptr == nullptr || *Ptr == StringMapImpl::getTombstoneVal())
-      ++Ptr;
+  template <bool ToConst,
+            typename = typename std::enable_if<!IsConst && ToConst>::type>
+  operator StringMapIterBase<ValueTy, ToConst>() const {
+    return StringMapIterBase<ValueTy, ToConst>(Ptr, true);
   }
-};
-
-template <typename ValueTy>
-class StringMapConstIterator
-    : public StringMapIterBase<StringMapConstIterator<ValueTy>,
-                               const StringMapEntry<ValueTy>> {
-  using base = StringMapIterBase<StringMapConstIterator<ValueTy>,
-                                 const StringMapEntry<ValueTy>>;
-
-public:
-  StringMapConstIterator() = default;
-  explicit StringMapConstIterator(StringMapEntryBase **Bucket,
-                                  bool NoAdvance = false)
-      : base(Bucket, NoAdvance) {}
 
-  const StringMapEntry<ValueTy> &operator*() const {
-    return *static_cast<const StringMapEntry<ValueTy> *>(*this->Ptr);
+  friend bool operator==(const StringMapIterBase &LHS,
+                         const StringMapIterBase &RHS) {
+    return LHS.Ptr == RHS.Ptr;
   }
-};
-
-template <typename ValueTy>
-class StringMapIterator : public StringMapIterBase<StringMapIterator<ValueTy>,
-                                                   StringMapEntry<ValueTy>> {
-  using base =
-      StringMapIterBase<StringMapIterator<ValueTy>, StringMapEntry<ValueTy>>;
 
-public:
-  StringMapIterator() = default;
-  explicit StringMapIterator(StringMapEntryBase **Bucket,
-                             bool NoAdvance = false)
-      : base(Bucket, NoAdvance) {}
-
-  StringMapEntry<ValueTy> &operator*() const {
-    return *static_cast<StringMapEntry<ValueTy> *>(*this->Ptr);
+  friend bool operator!=(const StringMapIterBase &LHS,
+                         const StringMapIterBase &RHS) {
+    return !(LHS == RHS);
   }
 
-  operator StringMapConstIterator<ValueTy>() const {
-    return StringMapConstIterator<ValueTy>(this->Ptr, true);
+private:
+  void AdvancePastEmptyBuckets() {
+    while (*Ptr == nullptr || *Ptr == StringMapImpl::getTombstoneVal())
+      ++Ptr;
   }
 };
 
 template <typename ValueTy>
 class StringMapKeyIterator
     : public iterator_adaptor_base<StringMapKeyIterator<ValueTy>,
-                                   StringMapConstIterator<ValueTy>,
+                                   StringMapIterBase<ValueTy, true>,
                                    std::forward_iterator_tag, StringRef> {
   using base = iterator_adaptor_base<StringMapKeyIterator<ValueTy>,
-                                     StringMapConstIterator<ValueTy>,
+                                     StringMapIterBase<ValueTy, true>,
                                      std::forward_iterator_tag, StringRef>;
 
 public:
   StringMapKeyIterator() = default;
-  explicit StringMapKeyIterator(StringMapConstIterator<ValueTy> Iter)
+  explicit StringMapKeyIterator(StringMapIterBase<ValueTy, true> Iter)
       : base(std::move(Iter)) {}
 
   StringRef operator*() const { return this->wrapped()->getKey(); }

@llvmbot
Copy link
Member

llvmbot commented Aug 30, 2025

@llvm/pr-subscribers-llvm-adt

Author: Kazu Hirata (kazutakahirata)

Changes

StringMap has four iterator classes:

  • StringMapIterBase
  • StringMapIterator
  • StringMapConstIterator
  • StringMapKeyIterator

This patch consolidates the first three into one class, namely
StringMapIterBase, adds a boolean template parameter to indicate
desired constness, and then use "using" directives to specialize the
common class:

using const_iterator = StringMapIterBase<ValueTy, true>;
using iterator = StringMapIterBase<ValueTy, false>;

just like how we simplified DenseMapIterator.

Remarks:

  • This patch drops CRTP and iterator_facade_base for simplicity. For
    fairly simple forward iterators, iterator_facade_base doesn't buy us
    much. We just have to write a few "using" directives and operator!=
    manually.

  • StringMapIterBase has a SFINAE-based constructor to construct a
    const iterator from a non-const one just like DenseMapIterator.

  • We now rely on compiler-generated copy and assignment operators.


Full diff: https://github.com/llvm/llvm-project/pull/156137.diff

3 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp (+1-1)
  • (modified) clang/lib/Driver/OffloadBundler.cpp (+1-2)
  • (modified) llvm/include/llvm/ADT/StringMap.h (+37-61)
diff --git a/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp b/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp
index 36b6007b58a51..4382f9df5336e 100644
--- a/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp
+++ b/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp
@@ -39,7 +39,7 @@ bool MultipleInheritanceCheck::getInterfaceStatus(const CXXRecordDecl *Node,
                                                   bool &IsInterface) const {
   assert(Node->getIdentifier());
   StringRef Name = Node->getIdentifier()->getName();
-  llvm::StringMapConstIterator<bool> Pair = InterfaceMap.find(Name);
+  auto Pair = InterfaceMap.find(Name);
   if (Pair == InterfaceMap.end())
     return false;
   IsInterface = Pair->second;
diff --git a/clang/lib/Driver/OffloadBundler.cpp b/clang/lib/Driver/OffloadBundler.cpp
index e83aee01b75cb..f69ac41dddb3e 100644
--- a/clang/lib/Driver/OffloadBundler.cpp
+++ b/clang/lib/Driver/OffloadBundler.cpp
@@ -1936,8 +1936,7 @@ Error OffloadBundler::UnbundleArchive() {
   /// Write out an archive for each target
   for (auto &Target : BundlerConfig.TargetNames) {
     StringRef FileName = TargetOutputFileNameMap[Target];
-    StringMapIterator<std::vector<llvm::NewArchiveMember>> CurArchiveMembers =
-        OutputArchivesMap.find(Target);
+    auto CurArchiveMembers = OutputArchivesMap.find(Target);
     if (CurArchiveMembers != OutputArchivesMap.end()) {
       if (Error WriteErr = writeArchive(FileName, CurArchiveMembers->getValue(),
                                         SymtabWritingMode::NormalSymtab,
diff --git a/llvm/include/llvm/ADT/StringMap.h b/llvm/include/llvm/ADT/StringMap.h
index dbc0485967ac6..aabe8cd7903c8 100644
--- a/llvm/include/llvm/ADT/StringMap.h
+++ b/llvm/include/llvm/ADT/StringMap.h
@@ -21,11 +21,11 @@
 #include "llvm/Support/PointerLikeTypeTraits.h"
 #include <initializer_list>
 #include <iterator>
+#include <type_traits>
 
 namespace llvm {
 
-template <typename ValueTy> class StringMapConstIterator;
-template <typename ValueTy> class StringMapIterator;
+template <typename ValueTy, bool IsConst> class StringMapIterBase;
 template <typename ValueTy> class StringMapKeyIterator;
 
 /// StringMapImpl - This is the base class of StringMap that is shared among
@@ -217,8 +217,8 @@ class LLVM_ALLOCATORHOLDER_EMPTYBASE StringMap
   using value_type = StringMapEntry<ValueTy>;
   using size_type = size_t;
 
-  using const_iterator = StringMapConstIterator<ValueTy>;
-  using iterator = StringMapIterator<ValueTy>;
+  using const_iterator = StringMapIterBase<ValueTy, true>;
+  using iterator = StringMapIterBase<ValueTy, false>;
 
   iterator begin() { return iterator(TheTable, NumBuckets == 0); }
   iterator end() { return iterator(TheTable + NumBuckets, true); }
@@ -431,14 +431,19 @@ class LLVM_ALLOCATORHOLDER_EMPTYBASE StringMap
   }
 };
 
-template <typename DerivedTy, typename ValueTy>
-class StringMapIterBase
-    : public iterator_facade_base<DerivedTy, std::forward_iterator_tag,
-                                  ValueTy> {
-protected:
+template <typename ValueTy, bool IsConst> class StringMapIterBase {
+  using EntryTy = std::conditional_t<IsConst, const StringMapEntry<ValueTy>,
+                                     StringMapEntry<ValueTy>>;
+
   StringMapEntryBase **Ptr = nullptr;
 
 public:
+  using iterator_category = std::forward_iterator_tag;
+  using value_type = EntryTy;
+  using difference_type = std::ptrdiff_t;
+  using pointer = value_type *;
+  using reference = value_type &;
+
   StringMapIterBase() = default;
 
   explicit StringMapIterBase(StringMapEntryBase **Bucket,
@@ -448,85 +453,56 @@ class StringMapIterBase
       AdvancePastEmptyBuckets();
   }
 
-  DerivedTy &operator=(const DerivedTy &Other) {
-    Ptr = Other.Ptr;
-    return static_cast<DerivedTy &>(*this);
-  }
-
-  friend bool operator==(const DerivedTy &LHS, const DerivedTy &RHS) {
-    return LHS.Ptr == RHS.Ptr;
-  }
+  reference operator*() const { return *static_cast<EntryTy *>(*Ptr); }
+  pointer operator->() const { return &operator*(); }
 
-  DerivedTy &operator++() { // Preincrement
+  StringMapIterBase &operator++() { // Preincrement
     ++Ptr;
     AdvancePastEmptyBuckets();
-    return static_cast<DerivedTy &>(*this);
+    return *this;
   }
 
-  DerivedTy operator++(int) { // Post-increment
-    DerivedTy Tmp(Ptr);
+  StringMapIterBase operator++(int) { // Post-increment
+    StringMapIterBase Tmp(*this);
     ++*this;
     return Tmp;
   }
 
-private:
-  void AdvancePastEmptyBuckets() {
-    while (*Ptr == nullptr || *Ptr == StringMapImpl::getTombstoneVal())
-      ++Ptr;
+  template <bool ToConst,
+            typename = typename std::enable_if<!IsConst && ToConst>::type>
+  operator StringMapIterBase<ValueTy, ToConst>() const {
+    return StringMapIterBase<ValueTy, ToConst>(Ptr, true);
   }
-};
-
-template <typename ValueTy>
-class StringMapConstIterator
-    : public StringMapIterBase<StringMapConstIterator<ValueTy>,
-                               const StringMapEntry<ValueTy>> {
-  using base = StringMapIterBase<StringMapConstIterator<ValueTy>,
-                                 const StringMapEntry<ValueTy>>;
-
-public:
-  StringMapConstIterator() = default;
-  explicit StringMapConstIterator(StringMapEntryBase **Bucket,
-                                  bool NoAdvance = false)
-      : base(Bucket, NoAdvance) {}
 
-  const StringMapEntry<ValueTy> &operator*() const {
-    return *static_cast<const StringMapEntry<ValueTy> *>(*this->Ptr);
+  friend bool operator==(const StringMapIterBase &LHS,
+                         const StringMapIterBase &RHS) {
+    return LHS.Ptr == RHS.Ptr;
   }
-};
-
-template <typename ValueTy>
-class StringMapIterator : public StringMapIterBase<StringMapIterator<ValueTy>,
-                                                   StringMapEntry<ValueTy>> {
-  using base =
-      StringMapIterBase<StringMapIterator<ValueTy>, StringMapEntry<ValueTy>>;
 
-public:
-  StringMapIterator() = default;
-  explicit StringMapIterator(StringMapEntryBase **Bucket,
-                             bool NoAdvance = false)
-      : base(Bucket, NoAdvance) {}
-
-  StringMapEntry<ValueTy> &operator*() const {
-    return *static_cast<StringMapEntry<ValueTy> *>(*this->Ptr);
+  friend bool operator!=(const StringMapIterBase &LHS,
+                         const StringMapIterBase &RHS) {
+    return !(LHS == RHS);
   }
 
-  operator StringMapConstIterator<ValueTy>() const {
-    return StringMapConstIterator<ValueTy>(this->Ptr, true);
+private:
+  void AdvancePastEmptyBuckets() {
+    while (*Ptr == nullptr || *Ptr == StringMapImpl::getTombstoneVal())
+      ++Ptr;
   }
 };
 
 template <typename ValueTy>
 class StringMapKeyIterator
     : public iterator_adaptor_base<StringMapKeyIterator<ValueTy>,
-                                   StringMapConstIterator<ValueTy>,
+                                   StringMapIterBase<ValueTy, true>,
                                    std::forward_iterator_tag, StringRef> {
   using base = iterator_adaptor_base<StringMapKeyIterator<ValueTy>,
-                                     StringMapConstIterator<ValueTy>,
+                                     StringMapIterBase<ValueTy, true>,
                                      std::forward_iterator_tag, StringRef>;
 
 public:
   StringMapKeyIterator() = default;
-  explicit StringMapKeyIterator(StringMapConstIterator<ValueTy> Iter)
+  explicit StringMapKeyIterator(StringMapIterBase<ValueTy, true> Iter)
       : base(std::move(Iter)) {}
 
   StringRef operator*() const { return this->wrapped()->getKey(); }

Copy link
Member

@kuhar kuhar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have dedicated unit tests for these iterators?

@kazutakahirata
Copy link
Contributor Author

Do we have dedicated unit tests for these iterators?

I wouldn't say llvm/unittests/ADT/StringMapTest.cpp is dedicated to iterators only, but it covers:

  • the empty map
  • the single-element map
  • for loop over the map
  • erase(iterator)
  • insert return value
  • keys()

I'd say this is pretty good coverage.

@kuhar
Copy link
Member

kuhar commented Aug 30, 2025

Maybe we could add some tests with is_same_v to check that the types are as expected, e.g., that dereferencing const iterator give you the right const reference etc?

@kazutakahirata
Copy link
Contributor Author

Maybe we could add some tests with is_same_v to check that the types are as expected, e.g., that dereferencing const iterator give you the right const reference etc?

@kuhar Good idea! I've added a couple of tests about iterator types. Please take a look. Thanks in advance!

@kazutakahirata
Copy link
Contributor Author

@kuhar I've added a few more tests on iterator traits. Also, I've corrected a bug where value_type was a const value type in const_iterator. IIUC, value_type is always a non-const value type.

Copy link

github-actions bot commented Aug 30, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@kazutakahirata kazutakahirata merged commit a428b30 into llvm:main Sep 1, 2025
9 checks passed
@kazutakahirata kazutakahirata deleted the cleanup_20250829_StringMap_Iter branch September 1, 2025 02:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang Clang issues not falling into any other category clang-tidy clang-tools-extra llvm:adt
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants