15

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?

Michael Kristofik
  • 34,290
  • 15
  • 75
  • 125
  • 3
    `std::vector vec; vec.size() * 2`, since we know that `Foo::size` always returns 2. :) – jalf Jul 08 '10 at 16:39

5 Answers5

31

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
  • 243,077
  • 51
  • 345
  • 550
  • typo: a semicolon is missing at the end of the body of the lambda (I can't edit myself). – rafak Jul 09 '10 at 12:51
8

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());
Michael Kristofik
  • 34,290
  • 15
  • 75
  • 125
  • Your solution is the most idiomatic one, of course, but a dumb iterator loop might be easier in such simple cases. – Philipp Jul 08 '10 at 15:14
  • +1 This would be improved by templating `SumSizes` for genericity, since all standard containers have a `size()` member function. – Jon Purdy Jul 08 '10 at 15:15
  • @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. – Michael Kristofik Jul 08 '10 at 15:29
  • 1
    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 Jul 08 '10 at 18:15
7

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
  • 5,501
  • 2
  • 19
  • 30
  • 1
    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 Jul 11 '10 at 13:08
  • I think this is the best answer. The problem is **what** to accumulate, which is addressed by a custom iterator, not **how** to accumulate, which is addressed by using a functor. The default accumulation behaviour (plus) _is_ what you want. Consider extending this problem to the inner product: the transformed iterator is reusable whereas the functor is not. A new functor for every algorithm would be required simply to redefine the default behaviour in terms of member size(). – Jeremy W. Murphy Sep 15 '13 at 07:14
6

using C++11 (and beyond) range-based for loop

std::vector<Foo> vFoo;
// populate vFoo with some values...
int totalSize = 0;
for (const auto& element: vFoo) {
    totalSize += element.size();
}
  • Like this solution. I find std::accumulate requires extra brain power: need to know the type of the container (Foo). Must not screw up the type of the initial value. Way longer to type, thus to read. Need to use cbegin/cend() and const on the argument of the lambda to ensure constness. – Mario Dec 04 '18 at 16:21
4

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
  • 48,066
  • 12
  • 84
  • 109