Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rework for loop over STL container to use functional techniques

I have a std::vector of pointers Person objects, which have a member function std::string getName() const. Using STL algorithms I want to count all the Person objects in the vector where getName() returns "Chad".

The behaviour simply iterating over the loop would be:

int num_chads = 0;
for(std::vector<Person *>::const_iterator it = vec.begin(); it != vec.end(); ++it)
{
    if((*it)->getName() == "Chad")
        ++num_chads;
}

I want to rework this so it uses all STL algorithms and functors etc (make it more functional-oriented). This is what I think I need to do:

const int num_chads = std::count_if(vec.begin(), vec.end(),
                                    std::bind1st(std::bind2nd(std::equal_to, mem_fun(Person::getName)), "Chad"));

As you can probably tell this doesn't work. Firstly, as I understand it, you can't use bind1st/bind2nd on binder1st/binder2nd objects as they are specifically designed to work with std::binary_functions. Secondly, and much more importantly, I don't think I am using the correct technique. I do want to bind one of the arguments to "Chad", but with the iterator argument I actually just want to transform the iterator value to a string before calling the bound version of equals_to.

I think it is possible to do this using Boost, but is it possible using just the core C++03 (i.e. no C++0x lambas!)?

EDIT: Can anyone come up with an example which does not use a user-defined predicate (i.e. just using the tools provided in the std toolkit)?

EDIT: While Matthieu's answer is a textbook answer for how to use functors in STL algorithms, Cubbi's answer came from the approach I was looking for (although Mathieu did answer before I edited the question to make it more specific, so apologies there!).

like image 669
Christopher Howlin Avatar asked Mar 16 '11 12:03

Christopher Howlin


3 Answers

I have always found lambdas relatively unreadable. I much prefer to write explicit types:

struct Named
{
  Named(char const* ref): _ref(ref) {}
  bool operator()(Person* p) const { return p && p->getName() == _ref; }
  char const* _ref;
};

size_t const c = std::count_if(vec.begin(), vec.end(), Named("Chad"));

Though the definition of Named is "out-of-line", a properly chosen name conveys the intention and hides the implementation details. Personally, I consider this a good thing, because then I am not distracting by implementation details or trying to figure out what's going on by reverse engineering the code (as evident as it might be).

like image 88
Matthieu M. Avatar answered Nov 18 '22 00:11

Matthieu M.


Since nobody posted the actual boost code yet, C++98 with boost:

ptrdiff_t num_chads = std::count_if(vec.begin(), vec.end(),
                      boost::bind(&Person::getName, _1) == "Chad");

test run https://ideone.com/PaVJe

As for pure C++, I don't think it's possible without the compose1 adaptor, present in STL but not in C++ stdlib...

and here it is (using GCC's implementation of STL)

ptrdiff_t num_chads = std::count_if(vec.begin(), vec.end(),
                     __gnu_cxx::compose1(
                         std::bind2nd(std::equal_to<std::string>(), "Chad"),
                         std::mem_fun(&Person::getName)));

test run: https://ideone.com/EqBS5

EDIT: corrected to account for Person*

like image 35
Cubbi Avatar answered Nov 18 '22 00:11

Cubbi


Use boost::bind, it's superior by quite some way to the existing Standard binding mechanisms. boost::bind is completely C++03 compatible.

like image 1
Puppy Avatar answered Nov 17 '22 23:11

Puppy