【问题标题】:Accessing std::map iterators inside range based loop like for_each在基于范围的循环内访问 std::map 迭代器,如 for_each
【发布时间】:2018-12-21 23:37:54
【问题描述】:

由于基于范围的循环很流行,所以我试图弄清楚如何以一种有效的方式编写以下代码,它使用基于范围的循环。

但请随意重构代码和/或建议另一种构造,这可能被认为是处理 std::map 的连续键的更好方法

C++11 兼容的答案是首选,但只是为了知识,请随意推荐任何来自 boost 或 C++14 的东西

void mapRangeLoop()
{
  std::map<std::string, std::vector<int>> infoMap;

// **** Piece of Code to be improved BEGIN *********
  std::for_each(infoMap.rbegin(), infoMap.rend(), [&infoMap](auto& it) {

    auto prev = std::prev(infoMap.find(it.first)); // <---I would like to avoid use of find() to get the iterator

    if (prev != infoMap.end() && isSubString(prev->first, it.first))
      for (auto& p : prev->second)
        processVectors(p, it.second);
  });
// **** Piece of Code to be improved END *********

// Same thing could be achieved through traditional for loop like this
  for (auto it = infoMap.rbegin(); it != infoMap.rend(); ++it)
  {
    auto prev = std::next(it);
    if (prev != infoMap.rend() && isSubString(prev->first, it->first))
      for (auto& p : prev->second)
        processVectors(p, it->second);
  }
}

//Just for completeness
bool isSubString(const std::string& s1, const std::string& s2)
{
  // returns true or false based on some logic
}

void processVectors(const int i, std::vector<int>& vec)
{
  // Some logic to modify vec based on value of i
}

【问题讨论】:

  • std::map 上的基于范围的循环将为您提供std::pair&lt;const Key,Value&gt;。
  • 看来你想要std::adjacent_find。

标签: c++ c++11 foreach


【解决方案1】:
template<class It>
struct range_t {
  It b, e;
  It begin() const { return b; }
  It end() const { return e; }
};
template<class It>
range_t<It> range( It s, It f ) { return {std::move(s), std::move(f)}; }

这是支持for(:) 在任意范围内循环的最小类型。

接下来我称之为索引迭代:

template<class V>
struct indexing_iteroid {
  using self=indexing_iteroid;
  V value;

  // * just returns a copy of value
  V operator*() const& { return value; }
  V operator*()&&{ return std::move(value); }

  // ++ forwards to value:
  self& operator++() {
    ++value;
    return *this;
  }
  self operator++(int) {
    auto r = *this;
    ++*this;
    return r;
  }

  // == compares values:
  friend bool operator==(self const& lhs, self const& rhs) {
    return lhs.value == rhs.value;
  }
  friend bool operator!=(self const& lhs, self const& rhs) {
    return lhs.value != rhs.value;
  }
};

如果你愿意,你可以将它扩展到一个成熟的输入迭代器;在实践中超出此范围的类别需要支持容器。

但是这个迭代器对于for(:) 循环来说已经足够好了,这些循环不是根据迭代器类别而是具体操作来指定的。

template<class It>
range_t< indexing_iterator<It> > index_over_range( range_t<It> r ) {
  return {{r.begin()}, {r.end()}};
}

这需要一个范围并在其中的迭代器上创建一个范围。

for (auto it : index_over_range( range( infoMap.begin(), infoMap.end() ) ) )
{
}

现在这是一个for(:) 循环,它将每个迭代器 访问到infoMap,而不是infoMap 的每个元素。

我们可以用更多样板来清理它

namespace adl {
  namespace it_details {
    using std::begin; using std::end;
    template<class X>
    auto adl_begin( X&& x )
    -> decltype( begin( std::forward<X>(x) ) )
    { return begin( std::forward<X>(x) ); }
    template<class X>
    auto adl_end( X&& x )
    -> decltype( end( std::forward<X>(x) ) )
    { return end( std::forward<X>(x) ); }
  }
  template<class X>
  auto begin( X&& x )
  -> decltype( it_details::adl_begin( std::forward<X>(x) ) )
  { return it_details::adl_begin( std::forward<X>(x) ); }
  template<class X>
  auto end( X&& x )
  -> decltype( it_details::adl_end( std::forward<X>(x) ) )
  { return it_details::adl_end( std::forward<X>(x) ); }
}

这给出了adl::begin( x )(和adl::end( x )),它在x 上调用begin 在std::begin 可用的上下文中,但begin 的参数相关查找也是如此。这几乎完全符合for(:) 循环如何找到它们的开始/结束迭代器。

template<class C>
auto iterators_of( C& c )
-> decltype( index_over_range( range( adl::begin(c), adl::end(c) ) ) )
{ return index_over_range( range( adl::begin(c), adl::end(c) ) ); }

现在iterators_of(someMap) 将所有迭代器的范围返回到someMap:

for (auto it : iterators_of( infoMap ) )
{
}

我们有漂亮、干净、高效的语法。

顺便说一句,indexing_iteroid 也可用于创建计数迭代(因此 for 循环访问 0、1、2、3),例如:

using counting_iteroid = indexing_iteroid<std::size_t>;
auto zero_to_ten = range( counting_iteroid{0}, counting_iteroid{11} );
for (auto x : zero_to_ten) {
   std::cout << x << "\n";
}

上面可能有一些错别字。

indexing_iteroid 可以包装一类类型;整数和迭代器都传递了这个概念。您可以增强indexing_iteroid 以更接近随机访问,但由于随机访问迭代器概念的标准化方式存在缺陷,它不会让您达到比输入迭代器更高的迭代器类别。 的 Rangesv3 可能会解决这个问题。

【讨论】:

    【解决方案2】:

    基于范围的循环和std::foreach 都旨在独立于其他元素自行处理元素。如果您需要其他元素的迭代器,则需要明确提供:

    auto next = infoMap.begin();
    for(auto& prev: infoMap)
    {
        ++next; // now points to element succeeding prev...
        if(next != infoMap.end())
        {
            // your code...
        }
    }
    

    虽然基于范围的循环不会以相反的顺序迭代,但最终的想法是相同的:

    auto next = infoMap.rbegin(); 
    std::for_each(infoMap.rbegin(), infoMap.rend(), [&infoMap, &next](auto& prev)
    {
        ++next; // again one in advance compared to prev...
                // (other direction than in above loop, though!)
        if(next != infoMap.rend())
        {
            // your code...
        }
    });
    

    不过,我个人会继续使用经典循环 - 它的优点是您可以将 if 移出它:

    if(infoMap.size() >= 2)
    {
        for (auto next = std::next(infoMap.rbegin()); next != infoMap.rend(); ++next)
        {
            prev = std::prev(next);
            // your code...
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-10-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-11
      • 2020-11-10
      相关资源
      最近更新 更多