3

I am trying to write an iterator adaptor that should call a member function (or access a member of the object) each time it is dereferenced. Here is an example of such an API:

vector<pair<int,int>> ps = {{1,"a"}, {2,"b"}, {3,"c"}};

// Pairs that represent ranges
auto rf = make_adaptor(ps.begin(), ps.end(), [](const auto& x) {return x.first;}
auto rs = make_adaptor(ps.begin(), ps.end(), [](auto& x) {return x.second;}

Should print out 123:

for_each(rf.first, rf.second, [](const auto& x){std::cout << x;});

Should set every second element of the pairs in ps:

for_each(rs.first, rs.second, [](auto& x){ x = "hello";});

I have tried writing an own iterator type together with a make_adaptor method, but I can't seem to get it to work:

template <typename Iterator, typename UnaryOp>
struct adaptor {
    using value_type = std::result_of<UnaryOp(typename Iterator::reference)>::type;
    using reference = value_type&;
    using pointer = value_type*;
    using difference_type = typename Iterator::difference_type;
    using iterator_category = typename Iterator::iterator_category;

    adaptor(){};
    adaptor(Iterator it, UnaryOp func) : _it(it), _func(func) {}

    reference operator*() const { return _func(*_it); }
    pointer operator->() const { return &_func(*_it); }

    bool operator==(const adaptor& other) const { return _it == other._it; }
    bool operator!=(const adaptor& other) const { return _it != other._it; }

    adaptor& operator++() {
        ++_it;
        return *this;
    }

    Iterator _it;
    UnaryOp _func;
};



template <typename Iterator, typename UnaryOp>
    auto make_adaptor(Iterator first, Iterator last, UnaryOp func) {
        return std::make_pair(adaptor<Iterator, UnaryOp>(first, func),
                              adaptor<Iterator, UnaryOp>(last, func));
    };

The reason for this is the following: Let's say I have an algorithm convex_hull which works on points. But now I have objects which contains points as a member (struct A { points pos;};). I want to call convex_hull on a collection of As.

fuji
  • 1,173
  • 1
  • 10
  • 27
  • 1
    Note: `operator->` is returning address of temporary as well as `operator*` is returning dangling reference. – Zereges Nov 29 '16 at 09:34
  • This is one of the problems I came up with, unfortunately I don't know how to solve these in a generic way. – fuji Nov 29 '16 at 09:36
  • I would definitely cache the result of function call, since you probably do not want to compute `func` everytime the iterator gets dereferenced. Then you can return the cahced variable (or it's address) and comment, that *incrementing the adaptor will invalidate it.* – Zereges Nov 29 '16 at 09:37
  • This sounds like a reasonable thing todo. I was hoping that this would compile down to the same machine code as: ``for(auto& x : ps) {x.second ="hello";}``. (I don't know if this is possible, but it would be very awesome). Because of this I think introducing additional caching logic would make this task harder for the compiler? – fuji Nov 29 '16 at 09:41

1 Answers1

0

With range-v3, you may do something similar to:

const std::vector<std::pair<int,const char*>> ps = {{1,"a"}, {2,"b"}, {3,"c"}};

for (const auto s : ps | ranges::view::keys)
{
    std::cout << " " << s;
}

for (const auto s : ps | ranges::view::transform([](const auto& p) { return p.first;} ))
{
    std::cout << " " << s;
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302