首页 > 解决方案 > 带有 std::map 的基于 C++ 范围的 for() 循环

问题描述

我有这些类型:

using key = std::string;
using stop_container = std::unordered_map<key, stop_o>;

using stop_name_const_reference = const std::string &;
using stops_const_reference = const stop_container &;
using timetable_const_reference = const timetable &;
using stop_const_reference = const stop_o&;

class timetable {
    public:
    stop_container timetable_stops;
    trip_container timetable_trips;
};

这些方法:

stops_const_reference stops(timetable_const_reference tt)
{
    return tt.timetable_stops;
}

stop_name_const_reference stop_name(stop_const_reference st)
{
    return st.first;
}

然后我的问题是循环范围:

for (auto&& stop : stops(tt))
    {
        auto&& stop_name_ = stop_name(stop);
    }

我可以这样重写它以便更好地理解:

for (auto&& stop : const std::unordered_map<key, stop_o> &)
    {
        auto&& stop_name_ = stop_name(stop);
    }

对于方法 stop_name 我需要const stop_o作为参数,但从循环中我得到const std::pair<const key, stop_o>. 我知道这就是地图在那里工作的方式,但是我能否以某种方式实现仅具有来自 pair 的值而根本不更改 for 循环?

标签: c++dictionaryfor-loop

解决方案


for (auto const& [key, stop] : stops(tt))
{
    auto&& stop_name_ = stop_name(stop);
}

这使用了一种称为“结构化绑定”的技术来将该对转换为对该对元素的两个引用。

在结构化绑定之前,您可以完成

for (auto const& elem : stops(tt))
{
    const auto& stop = elem.second;
    auto&& stop_name_ = stop_name(stop);
}

如果要保持 for 循环的主体和初始语句不变,则必须更改stops.

如果stop_o可以免费复制:

std::vector<stop_o> stops(timetable_const_reference tt);

写起来不应该很昂贵。

如果没有,你必须花哨。

template<class T>
struct pseudo_pointer {
  T t;
  T* operator->() { return std::addressof(t); }
};

template<class F, class It>
struct mapping_input_iterator {
  using iterator_category = std::input_iterator_tag;
  using reference = std::invoke_result_t<F, typename std::iterator_traits<It>::reference>;
  using value_type = std::decay_t<reference>;
  using difference_type = typename std::iterator_traits<It>::difference_type;
  using pointer = pseudo_pointer<reference>;
  F f;
  It it;
  mapping_input_iterator& operator++() {
    ++it; return *this;
  }
  mapping_input_iterator operator++(int) {
    auto retval = *this; ++(*this); return retval;
  }
  bool operator==(mapping_input_iterator const& other) const {
    return it == other.it;
  }
  bool operator!=(mapping_input_iterator const& other) const {
    return !(*this == other);
  }
  reference operator*() const {return f(*it);}
  pointer operator->() const {return {f(*it)};}
};
template<class F, class It>
mapping_input_iterator(F, It)->mapping_input_iterator<F, It>;

template<class It, class Sentinal=It>
struct range {
  It b;
  Sentinal e;
  It begin() const { return b; }
  Sentinal end() const { return e; }
};
template<class It>
range(It, It)->range<It, It>;

然后在上述工作之上编写一些实用函数:

template<class C>
auto get_keys( C const& c ) {
    auto key_getter = [](auto const& pair)->decltype(auto) { return pair.first; };
    using std::begin; using std::end;
    mapping_input_iterator b{ key_getter, begin(c) };
    mapping_input_iterator e{ key_getter, end(c) };

    return range{std::move(b),std::move(e)};
}
template<class C>
auto get_values( C const& c ) {
    auto value_getter = [](auto const& pair)->decltype(auto) { return pair.second; };
    using std::begin; using std::end;
    mapping_input_iterator b{ value_getter, begin(c) };
    mapping_input_iterator e{ value_getter, end(c) };

    return range{std::move(b),std::move(e)};
}

现在我们可以做这个魔术:

auto stops(timetable_const_reference tt)
{
  return get_values(tt.timetable_stops);
}

如果您害怕上面的代码,那么(在最近的 C++ 标准中)有类似的实用程序,这是合理boost的。std::range

活生生的例子


请注意,当一个稍微简单的版本可以工作时,我以一些小方式“正确”地做了一堆东西。

要执行for(:)循环,您不需要实际的迭代器,您只需要一个可以说服for(:)循环生成代码的迭代器。我实际上写了一个真正的迭代器包装器。

template<class It>
range(It, It)->range<It, It>;

故意不允许您将哨兵类型推断为与迭代器不同;这很容易意外出错,如果你想要一个哨兵,你必须明确地传递它。尽管如此,我range还是支持单独的结束前哨,因为我写不简单。

OTOH,我可能应该使值/键获取器不是匿名的本地 lambda,因此人们可以命名get_keys返回的范围类型而无需执行 decltype。那好吧。


推荐阅读