在基于范围的循环(如for_each)中访问std :: map迭代器

时间:2018-07-13 14:40:30

标签: c++ c++11 foreach

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

但是请随时重构代码和/或建议使用另一种构造,该构造可能被认为是处理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
}

2 个答案:

答案 0 :(得分:0)

基于范围的循环和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...
    }
}

答案 1 :(得分:0)

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;
  }
};

如果您愿意,可以将其扩展为成熟的输入迭代器;实际上,超出此范围的目录需要一个后备容器。

但是这个iteroid足以应付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(:)循环,它将每个 iterator 都访问到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 )),它在begin可用的上下文中在x上调用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可以解决此问题。