views:

144

answers:

4

Let's say I have the following object:

struct Foo
{
    int size() { return 2; }
};

What's the best way (most maintainable, readable, etc.) to get the total size of all objects in a vector<Foo>? I'll post my solution but I'm interested in better ideas.

Update:

So far we have:

  • std::accumulate and a functor
  • std::accumulate and a lambda expression
  • plain ol' for-loop

Are there any other workable solutions? Can you make something maintainable using boost::bind or std::bind1st/2nd?

+5  A: 

Use std::accumulate and a functor.

#include <functional>
#include <numeric>

struct SumSizes : public std::binary_function<int, Foo, int>
{
    int operator()(int total, const Foo& elem) const
    {
        return total + elem.size();
    }
};

std::vector<Foo> vf;

// do something to populate vf

int totalSize = std::accumulate(vf.begin(),
                                vf.end(),
                                0, 
                                SumSizes());
Kristo
Your solution is the most idiomatic one, of course, but a dumb iterator loop might be easier in such simple cases.
Philipp
+1 This would be improved by templating `SumSizes` for genericity, since all standard containers have a `size()` member function.
Jon Purdy
@Jon, I think you may have misunderstood the question. The point was not to get the size of the container, but to sum the result of a member function of all elements. Perhaps `size` was a poor name for such a function.
Kristo
@Kristo: No, I understood the question, and just thought I'd make an odd point because your example happens to use the identifier `size()`. If made generic, `SumSizes` would sum the individual sizes of each element of a container of containers (or sequences, for example `std::string`). Incidentally. :P
Jon Purdy
+3  A: 

In addition to your own suggestion, if your compiler supports C++0x lambda expressions, you can use this shorter version:

std::vector<Foo> vf;

// do something to populate vf


int totalSize = std::accumulate(vf.begin(),
                                vf.end(),
                                0, 
                                [](int sum, const Foo& elem){ return sum + elem.size()});
jalf
typo: a semicolon is missing at the end of the body of the lambda (I can't edit myself).
rafak
+3  A: 

Here is the down-to-earth solution:

typedef std::vector<Foo> FooVector;
FooVector vf;
int totalSize = 0;
for (FooVector::const_iterator it = vf.begin(); it != vf.end(); ++it) {
  totalSize += it->size();
}
Philipp
So much easier to read than the other, functional solutions.
Jon
+3  A: 

I find Boost iterators elegants, although they can be a bit verbose (range-based algorithms would make this better). In this case transform iterators can do the job:

#include <boost/iterator/transform_iterator.hpp>
//...

int totalSize = std::accumulate(
    boost::make_transform_iterator(vf.begin(), std::mem_fn(&Foo::size)),
    boost::make_transform_iterator(vf.end(), std::mem_fn(&Foo::size)),0);

Edit: replaced "boost::bind(&Foo::size,_1)" by "std::mem_fn(&Foo::size)"

Edit: I just found that the Boost.Range library has been updated to introduce range algorithms! Here is a new version of the same solution:

#include <boost/range/distance.hpp> // numeric.hpp needs it (a bug?)
#include <boost/range/numeric.hpp> // accumulate
#include <boost/range/adaptor/transformed.hpp> // transformed
//...
int totalSize = boost::accumulate(
    vf | boost::adaptors::transformed(std::mem_fn(Foo::size)), 0);

Note: the performances are approximately the same (see my comment): internally, transformed uses transorm_iterator.

rafak
I did timings comparing this solution and the direct one, and unfortunately this one is slower (I found a factor between 2 and 5). However this may not be a concern.
rafak