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

Accessing std::map iterators inside range based loop like for_each

本文关键字:each 中的 迭代器 for std map 循环 于范围 范围 访问      更新时间:2023-10-16

由于基于范围的循环很流行,所以我试图找出如何以高效的方式编写以下代码,即使用基于范围的环路。

但是,请随意重组代码和/或提出另一种构造,这可能被认为是处理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::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...
}
}
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)}; }

这是支持任意范围上的CCD_ 3循环的最小类型。

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

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(:)循环,它访问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) ); }
}

这给出了在std::begin可用的上下文中调用x上的beginadl::begin( x )(和adl::end( x )(,但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还可以用来创建一个计数迭代(因此对于循环访问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增强为更接近随机访问,但由于随机访问迭代器概念的标准化方式存在缺陷,它无法使您达到比输入迭代器更高的迭代器类别。c++2a的Rangesv3可能会解决这个问题。