Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
308 views
in Technique[技术] by (71.8m points)

dictionary - C++ range-based for() loop with std::map

I have these types:

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

these methods:

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

Then my problem is with range for loop:

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

I can rewrite it like this for better understanding:

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

For method stop_name I need const stop_o as an argument, but from the loop I get const std::pair<const key, stop_o>. I know that this is how the maps are working there, but can I somehow achieve to have only the value from pair there AND WITHOUT CHANGING the for loop at all?


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)
for (auto const& [key, stop] : stops(tt))
{
    auto&& stop_name_ = stop_name(stop);
}

this uses a technique known as "structured bindings" to turn the pair into two references to the elements of the pair.

Prior to structured bindings, you could have done

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

If you want to keep the body and initial statement of the for loop unchanged, you would have to change stops.

If stop_o is free to copy:

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

that shouldn't be expensive to write.

If not, you have to get fancy.

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

then write some utility functions on top of the above work:

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

now we can do this magic:

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

there are similar utilities in boost and in std::range (in a recent C++ standard) if you are afraid of the above code, which is reasonable.

Live example.


Note that I did a bunch of stuff above "properly" in a few small ways, when a slightly simpler version would have worked.

To do a for(:) loop you don't need an actual iterator, you just need one that can convince the for(:) loop generated code. I actually wrote a real iterator wrapper.

Also

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

intentionally doesn't let you deduce the sentinal type as different than the iterator; that is too easy to get wrong by accident, if you want a sentinal you have to pass it in explicitly. Despite this my range supports a seperate end sentinal, because I'm writing it right not simply.

OTOH, I should probably make the value/key getters not be an anonymous local lambda, so people can name the range type that get_keys returns without doing a decltype. Oh well.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...