简体   繁体   中英

c++ how to create iterator over one field of a struct vector

I have a structure of all primitive types, like so:

struct record {
    int field1;
    double field2;
}

I have a vector of instances of this struct, like so:

vector<record> records;

Is it possible/what is the best way to create a vector<int>::iterator that will iterate over field1 ? What about if I used an array record records[n] ? I need something that looks like vector<int>::iterator .

EDIT: I need something that IS a vector<int>::iterator .

Making an iterator adaptor

First of all, the simplest solution is to iterate over the container and access the fields from the iterator.

for (auto&& r : records) {
    int value = r.field1;
    /* do something with 'value' */
}

Anyway, if you really want an iterator that returns field1 when dereferenced you can easily implement an iterator adaptor that derives from the container's own iterator.

struct my_it : public std::vector<record>::iterator {
    using std::vector<record>::iterator::iterator;
    int operator*() { return std::vector<record>::iterator::operator*().field1; }
};

And use it like this:

for (my_it it = std::begin(records); it != std::end(records); ++it) {
    int value = *it; // Dereferencing now returns 'field1'.
}

Is it an XY problem?

As explained in the answer by Ben Voigt , there is no way to create a std::vector<int>::iterator that iterates over something else than int elements stored in a contiguous array.

If you need a function that takes input iterators then make it a template function . This way it will work with iterators for any container type. This is how all the algorithms in the standard library is implemented.

template <typename InputIt>
void func(InputIt first, InputIt last) {
    for (; first != last; ++first) {
        value = *it; // Dereferences input iterator of any type.
    }
}

Iterators should be interfaced through their operations (ie read, increment, decrement, random access), not their explicit type. Iterators are categorized by the number of operations they support.

For instance, if you need to iterate over a range and read all the values in a single pass then you want input iterators as arguments. The type of the iterator itself should be irrelevant.

See this for more info about iterator categories

You're out of luck.

vector<int>::iterator is not polymorphic 1 . There's no place to reach in and change the pointer step size. vector<int>::iterator iterates a sequence of contiguous int objects, only, and your int objects are not stored contiguously.

This is why all the C++ standard algorithms are templated to accept iterators of any type. If you make your function a template accepting arbitrary iterator types, you can use an iterator adaptor like the one Snps wrote.


1 Polymorphism is slow relative to pointer arithmetic, no one would use std::vector if it didn't have similar performance to a plain array

You can use lambdas on your original vector .

For example:

for_each(records.begin(), records.end(), [](record& foo){/*operate on foo.field1 here*/});

Note that the vast majority of other algorithms also accept lambdas, so you can just iterate over your original vector using the lambda to access just field1 .

I'm being presumptuous here but the behavior you are looking for seems to closely resemble that of a map so you might wanna have a look at that.

I suppose that you're into Boost and evil you could also cobble something together with this: http://www.boost.org/doc/libs/1_57_0/libs/range/doc/html/range/reference/adaptors/reference/strided.html

It seems like you are asking for something like this

for(std::vector<record>::iterator i=records.begin(), end=records.end(); i!=end; ++i)
{
    std::cout << i->field1 << std::endl;
}

Or in C++11

for(auto i=records.begin(), end=records.end(); i!=end; ++i)
{
    std::cout << i->field1 << std::endl;
}

If you really want a member iterator, you can do something like this:

template <class M>
struct member_traits;

template <class T, class C>
struct member_traits<T C::*>
{
    using class_type = C;
    using return_type = T;
};

template <class Iterator, class C, class M>
struct member_iterator : Iterator
{
public:
    using Iterator::Iterator;

    template <class I, class Member>
    member_iterator(I&& begin, Member&& member)
        : std::vector<C>::iterator(std::forward<I>(begin))
        , member(std::forward<Member>(member))
    {
        static_assert(std::is_member_pointer<Member>::value,
                      "Member must be dereferenceable");
    }

    typename member_traits<M>::return_type& operator*()
    {
        return (*static_cast<Iterator&>(*this)).*member;
    }
private:
    M member;
};

template <class Member, class Iterator>
auto make_member_iterator(Member&& member, Iterator&& it)
    -> member_iterator<std::decay_t<Iterator>, typename member_traits<Member>::class_type, std::decay_t<Member>>
{
    return {std::forward<Iterator>(it), std::forward<Member>(member)};
}

struct Record
{
    int field1;
    double field2;
};

int main()
{
    std::vector<Record> v { {1, 1.0}, {2, 2.0}, {3, 3.0} };

    for (auto it = make_member_iterator(&Record::field1, v.begin()); it != v.end(); ++it)
    {
        std::cout << *it << " ";
    }
}

Demo

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM