diff --git a/src/core/ext/filters/client_channel/client_channel.cc b/src/core/ext/filters/client_channel/client_channel.cc
index 01825a14eb9aa8330302a01e21ad0f4ebc614186..450bb5fd04b4ae8137d9d9d4c8c144546ad98691 100644
--- a/src/core/ext/filters/client_channel/client_channel.cc
+++ b/src/core/ext/filters/client_channel/client_channel.cc
@@ -376,35 +376,39 @@ class CallData {
                  GRPC_ERROR_NONE);
     }
 
-    Iterator Begin() const override {
-      static_assert(sizeof(grpc_linked_mdelem*) <= sizeof(Iterator),
+    iterator begin() const override {
+      static_assert(sizeof(grpc_linked_mdelem*) <= sizeof(intptr_t),
                     "iterator size too large");
-      return reinterpret_cast<Iterator>(batch_->list.head);
+      return iterator(this, reinterpret_cast<intptr_t>(batch_->list.head));
     }
-    bool IsEnd(Iterator it) const override {
-      return reinterpret_cast<grpc_linked_mdelem*>(it) == nullptr;
-    }
-    void Next(Iterator* it) const override {
-      *it = reinterpret_cast<Iterator>(
-          reinterpret_cast<grpc_linked_mdelem*>(*it)->next);
-    }
-    StringView Key(Iterator it) const override {
-      return StringView(
-          GRPC_MDKEY(reinterpret_cast<grpc_linked_mdelem*>(it)->md));
-    }
-    StringView Value(Iterator it) const override {
-      return StringView(
-          GRPC_MDVALUE(reinterpret_cast<grpc_linked_mdelem*>(it)->md));
+    iterator end() const override {
+      static_assert(sizeof(grpc_linked_mdelem*) <= sizeof(intptr_t),
+                    "iterator size too large");
+      return iterator(this, 0);
     }
 
-    void Erase(Iterator* it) override {
+    iterator erase(iterator it) override {
       grpc_linked_mdelem* linked_mdelem =
-          reinterpret_cast<grpc_linked_mdelem*>(*it);
-      *it = reinterpret_cast<Iterator>(linked_mdelem->next);
+          reinterpret_cast<grpc_linked_mdelem*>(GetIteratorHandle(it));
+      intptr_t handle = reinterpret_cast<intptr_t>(linked_mdelem->next);
       grpc_metadata_batch_remove(batch_, linked_mdelem);
+      return iterator(this, handle);
     }
 
    private:
+    intptr_t IteratorHandleNext(intptr_t handle) const override {
+      grpc_linked_mdelem* linked_mdelem =
+          reinterpret_cast<grpc_linked_mdelem*>(handle);
+      return reinterpret_cast<intptr_t>(linked_mdelem->next);
+    }
+    std::pair<StringView, StringView> IteratorHandleGet(
+        intptr_t handle) const override {
+      grpc_linked_mdelem* linked_mdelem =
+          reinterpret_cast<grpc_linked_mdelem*>(handle);
+      return std::make_pair(StringView(GRPC_MDKEY(linked_mdelem->md)),
+                            StringView(GRPC_MDVALUE(linked_mdelem->md)));
+    }
+
     CallData* calld_;
     grpc_metadata_batch* batch_;
   };
diff --git a/src/core/ext/filters/client_channel/lb_policy.h b/src/core/ext/filters/client_channel/lb_policy.h
index dc4bf51cfce9a34eb5072b57ce784eeebcdcba44..bf6efb00fc1ef56a97478606f7486193977ebc4a 100644
--- a/src/core/ext/filters/client_channel/lb_policy.h
+++ b/src/core/ext/filters/client_channel/lb_policy.h
@@ -21,6 +21,8 @@
 
 #include <grpc/support/port_platform.h>
 
+#include <iterator>
+
 #include "src/core/ext/filters/client_channel/server_address.h"
 #include "src/core/ext/filters/client_channel/service_config.h"
 #include "src/core/ext/filters/client_channel/subchannel_interface.h"
@@ -119,10 +121,31 @@ class LoadBalancingPolicy : public InternallyRefCounted<LoadBalancingPolicy> {
   /// Implemented by the client channel and used by the SubchannelPicker.
   class MetadataInterface {
    public:
-    // Implementations whose iterators fit in intptr_t may internally
-    // cast this directly to their iterator type.  Otherwise, they may
-    // dynamically allocate their iterators and store the address here.
-    typedef intptr_t Iterator;
+    class iterator
+        : public std::iterator<std::input_iterator_tag,
+                               std::pair<StringView, StringView>,  // value_type
+                               std::ptrdiff_t,  // difference_type
+                               std::pair<StringView, StringView>*,  // pointer
+                               std::pair<StringView, StringView>&   // reference
+                               > {
+     public:
+      iterator(const MetadataInterface* md, intptr_t handle)
+          : md_(md), handle_(handle) {}
+      iterator& operator++() {
+        handle_ = md_->IteratorHandleNext(handle_);
+        return *this;
+      }
+      bool operator==(iterator other) const {
+        return md_ == other.md_ && handle_ == other.handle_;
+      }
+      bool operator!=(iterator other) const { return !(*this == other); }
+      value_type operator*() const { return md_->IteratorHandleGet(handle_); }
+
+     private:
+      friend class MetadataInterface;
+      const MetadataInterface* md_;
+      intptr_t handle_;
+    };
 
     virtual ~MetadataInterface() = default;
 
@@ -134,15 +157,22 @@ class LoadBalancingPolicy : public InternallyRefCounted<LoadBalancingPolicy> {
     virtual void Add(StringView key, StringView value) = 0;
 
     /// Iteration interface.
-    virtual Iterator Begin() const = 0;
-    virtual bool IsEnd(Iterator it) const = 0;
-    virtual void Next(Iterator* it) const = 0;
-    virtual StringView Key(Iterator it) const = 0;
-    virtual StringView Value(Iterator it) const = 0;
-
-    /// Removes the element pointed to by \a it, which is modified to
-    /// point to the next element.
-    virtual void Erase(Iterator* it) = 0;
+    virtual iterator begin() const = 0;
+    virtual iterator end() const = 0;
+
+    /// Removes the element pointed to by \a it.
+    /// Returns an iterator pointing to the next element.
+    virtual iterator erase(iterator it) = 0;
+
+   protected:
+    intptr_t GetIteratorHandle(const iterator& it) const { return it.handle_; }
+
+   private:
+    friend class iterator;
+
+    virtual intptr_t IteratorHandleNext(intptr_t handle) const = 0;
+    virtual std::pair<StringView /*key*/, StringView /*value */>
+    IteratorHandleGet(intptr_t handle) const = 0;
   };
 
   /// Arguments used when picking a subchannel for a call.
diff --git a/test/core/util/test_lb_policies.cc b/test/core/util/test_lb_policies.cc
index c4aab3fc3ac540ede8c9895c6094008910da6b8c..684ee656b756a57167d704b19f562791e947ff9b 100644
--- a/test/core/util/test_lb_policies.cc
+++ b/test/core/util/test_lb_policies.cc
@@ -201,13 +201,10 @@ class InterceptRecvTrailingMetadataLoadBalancingPolicy
   };
 
   static void LogMetadata(MetadataInterface* metadata) {
-    for (MetadataInterface::Iterator it = metadata->Begin();
-         !metadata->IsEnd(it); metadata->Next(&it)) {
+    for (const auto& p : *metadata) {
       gpr_log(GPR_INFO, "  \"%.*s\"=>\"%.*s\"",
-              static_cast<int>(metadata->Key(it).size()),
-              metadata->Key(it).data(),
-              static_cast<int>(metadata->Value(it).size()),
-              metadata->Value(it).data());
+              static_cast<int>(p.first.size()), p.first.data(),
+              static_cast<int>(p.second.size()), p.second.data());
     }
   }
 };