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.