C++ iterate over vector of objects and apply STL algorithms to member variables

岁酱吖の 提交于 2020-01-03 03:30:10

问题


What again is the quick way to iterate over a vector of custom objects but access only a single member in order to apply general STL-algorithms?

struct Foo
{
    std::string a;
    double b = 1.0;
};

int main()
{
    std::vector<Foo> fooVector(20);

    // iterate over all members b -- as if we were iterating over a std::vector<double>
    std::discrete_distribution<int> dist(/*??*/, /*??*/);
}

With "quick" I mean

  • no custom iterator -- or only a very lightweight one (--a few lines of code, no boost iterator_facade, etc.),
  • no modification of the dereferencing operator (--not that quick).

回答1:


Here is the solution mentioned in my comment:

struct LightIterator : public std::vector<Foo>::iterator
{
    LightIterator(std::vector<Foo>::iterator it) : std::vector<Foo>::iterator(it) {}
    double& operator*() { return std::vector<Foo>::iterator::operator*().b; }
};

Which you can use like this:

Run It Online

std::accumulate(LightIterator{fooVector.begin()},
                LightIterator{fooVector.end()},
                0.0);

EDIT: @TartanLlama is right about the issue related to the actual type of std::vector<Foo>::iterator.

As an attempt to have a more generic solution, I suggest that you define a wrapper iterator class for when std::vector<Foo>::iterator is a raw pointer. Something like:

(notice that I'm now allowing arbitrary attributes to be selected. More on that later)

template <
    typename PointerType,
    typename ItemType,
    typename AttributeType
>
struct LightIterator_FromPointer : public std::iterator<std::input_iterator_tag,
                                                        std::remove_pointer_t<PointerType>>
{
    PointerType it;
    AttributeType ItemType::* pointerToAttribute;
    LightIterator_FromPointer(PointerType it_, AttributeType ItemType::* pointerToAttribute_)
    : it(it_)
    , pointerToAttribute(pointerToAttribute_)
    {}

    AttributeType& operator*() { return it->*pointerToAttribute; }
    AttributeType* operator->() { return it; }

    // input iterator boilerplate: http://en.cppreference.com/w/cpp/concept/InputIterator
    using this_t = LightIterator_FromPointer<PointerType, ItemType, AttributeType>;  // less typing...
    LightIterator_FromPointer(const this_t& other) : it(other.it) {}
    bool operator!=(const this_t& other) const { return it != other.it; }
    this_t& operator++() { ++it; return *this; }
    this_t operator++(const int) { return {it++}; }
};

While still keeping the original "minimal" light iterator for when std::vector<Foo>::iterator is actually a class:

template <
    typename IteratorType,
    typename ItemType,
    typename AttributeType
>
struct LightIterator_FromClass : public IteratorType
{
    AttributeType ItemType::* pointerToAttribute;
    LightIterator_FromClass(IteratorType it_, AttributeType ItemType::* pointerToAttribute_)
    : IteratorType(it_)
    , pointerToAttribute(pointerToAttribute_)
    {}
    AttributeType& operator*() { return IteratorType::operator*().*pointerToAttribute; }
};

Finally, in order to abstract the details of the light iterator type that should be used on the call site, you can define a make_iterator() function that takes care of everything:

template <
    typename IteratorType,
    typename ItemType,
    typename AttributeType
>
typename std::conditional<std::is_pointer<IteratorType>::value,
    LightIterator_FromPointer<IteratorType, ItemType, AttributeType>,
    LightIterator_FromClass<IteratorType, ItemType, AttributeType>
>::type
make_iterator(IteratorType it, AttributeType ItemType::* pointerToAttribute)
{
    return typename std::conditional<std::is_pointer<IteratorType>::value,
        LightIterator_FromPointer<IteratorType, ItemType, AttributeType>,
        LightIterator_FromClass<IteratorType, ItemType, AttributeType>
    >::type(it, pointerToAttribute);
}

The result is a simple calling syntax which (bonus) allows for selecting any attribute, not only Foo::b.

Run It Online

// light iterator from an actual iterator "class"
{
    std::vector<Foo> fooVector(20);

    double acc = std::accumulate(make_iterator(fooVector.begin(), &Foo::b),
                                 make_iterator(fooVector.end(),  &Foo::b),
                                 0.0);
    cout << acc << endl;
}

// light iterator from a "pointer" iterator
{
    std::array<Foo, 20> fooVector;

    double acc = std::accumulate(make_iterator(fooVector.begin(), &Foo::b),
                                 make_iterator(fooVector.end(), &Foo::b),
                                 0.0);
    cout << acc << endl;
}



回答2:


The constructor of std::discrete_distribution<...> doesn't support any explicit way to project values (like a function object optionally applied to transform the result of *it before being used). As a result I think there are three basic approaches:

  1. Use an intermediate std::vector<double> to obtain a range whose iterators yield doubles:

    std::vector<double> tmp; // reserve() as desired
    std::transform(fooVector.begin(), fooVector.end(),
                   std::back_inserter(tmp),
                   [](Foo const& f){ return f.b; });
    std::discrete_distribution<int> d(tmp.begin(), tmp.end());
    
  2. It may viable to use a conversion operator on Foo to convert into a double:

    class Foo {
        // ...
        operator double() const { return this->b; }
    };
    // ...
    std::discrete_distribution<int> d(fooVector.begin(), fooVector.end());
    
  3. Create a wrapper for the iterator and use that. It doesn't need anything fancy but putting together a simple input iterators is still comparatively involved:

    template <typename InIt>
    class project_iterator {
        InIt it;
    public:
        explicit project_iterator(InIt it): it(it) {}
        double operator*() const { return *this->it; }
        project_iterator& operator++() { ++this->it; return *this; }
        project_iterator  operator++(int) {
            project_iterator rc(*this);
            this->operator++();
            return *this;
        }
        bool operator==(project_iterator const& other) const {
            return this->it == other.it;
        }
        bool operator!=(project_iterator const& other) const {
            return !(*this == other);
        }
    };
    template <typename It>
    project_iterator<It> project(It it) {
        return project_iterator<It>(it);
    }
    namespace std {
        template <typename It>
        class iterator_traits<project_iterator<It> {
        public:
            typedef typename std::iterator_traits<It>::difference_type difference_type;
            typedef double value_type;
            typedef double& reference;
            typedef double* pointer;
            typedef std::input_iterator_tag iterator_category;
        }
    }
    // ...
    std::discrete_distribution<int> d(project(fooVector.begin()), project(fooVector.end());
    

There are, obviously, variation on these approaches but I don't think there is anything else which can be done cleverly. What is missing is essentially a general approach to have projections with sequences (I'm normally referring to them as property maps).



来源:https://stackoverflow.com/questions/34198818/c-iterate-over-vector-of-objects-and-apply-stl-algorithms-to-member-variables

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!