1

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?

2
  • 3
    Are you looking for for (const auto& [_, stop] : stops(tt)) or auto&& stop_name_ = stop_name(stop->second);? Commented Jan 8, 2021 at 15:24
  • for (const auto& [_, stop] : stops(tt)) Commented Jan 8, 2021 at 15:27

1 Answer 1

4
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.

Sign up to request clarification or add additional context in comments.

3 Comments

what if I cant change the loop at all? I can only change the methods and containers
then obviously you can't change what it assigns the looped pairs to...
@Rikib161999 You cannot change what code does without changing code somewhere. What are you allowed to change? If you can't change anything, then no, you can't change what stuff does without changing anything. Do you want to change the function stops and its return value only or something? (I'd view that as changing the loop, but maybe you don't)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.