问题
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:
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());
It may viable to use a conversion operator on
Foo
to convert into adouble
:class Foo { // ... operator double() const { return this->b; } }; // ... std::discrete_distribution<int> d(fooVector.begin(), fooVector.end());
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