Posted by
| Nick Gammon
Australia (23,046 posts) Bio
Forum Administrator |
Message
| After about 2 days of research I have finally worked out how to make the above examples more general. :)
What I disliked about my earlier examples of finding a player by name was you had to use one of:
- A specially-constructed function object (player_name_equal) whose only job was to test if a player name equalled something; or
- An operator== which looks a bit weird, and in any case would only allow for one sort of thing to be tested (eg. if you had 10 integer types (as member values), overloading operator== (int) would only work for one of them); or
- A member function (IsName) which was only added to the class to find if a name equalled something.
I thought all of the above lacked generality, and also couldn't be used - without more work - to find other things (eg. all players with age < 22).
The example below compiles and runs, with a couple of caveats. ;)
The Microsoft Visual C++ version 6 compiler, as shipped, did not support the use of mem_fun_ref in the way I wanted to use it (kept giving compiler errors) and did not have support for compose1, which was also needed.
I eventually did what was recommended in Scott Meyers' book "Effective STL" and download the SGI version of STL from:
http://www.sgi.com/tech/stl/
I then changed the MS compiler's include path to look first in the SGI STL directory, and then its normal directories (Tools menu -> Options -> Directories).
This then allowed the program below to compile. However under gcc on Red Hat Linux it did not support compose1 (although the other parts worked) because that was an STL extension. Thus, the code below has the appropriate template hard-coded into it. You could avoid that by, for instance, using the SGI version of STL under Linux as well.
How it works
The line that took a long time to get working is this one:
ShowDetails (find_if (v.begin (), v.end (),
compose1 (bind2nd (equal_to<string> (), "Nick"),
mem_fun_ref ( &player::GetName ))
));
I wanted to be able to find anything (in this case "Nick") using any member function (in this case GetName) and use that in any comparison (in this case equal_to).
The line could be simplified a bit to be this:
vector<player>::const_iterator i;
i = find_if (v.begin (), v.end (),
compose1 (bind2nd (equal_to<string> (), "Nick"),
mem_fun_ref ( &player::GetName ))
);
The important part is the predicate (something returning true or false) that is passed to find_if. That is:
compose1 (bind2nd (equal_to<string> (), "Nick"),
mem_fun_ref ( &player::GetName ))
What we need here are two function calls:
- GetName - to get each player's name
- equal_to - to compare it to our target name
mem_fun_ref makes a temporary function object which allows us to call the member function by reference (as opposed to by a pointer) hence its name.
We then need to call equal_to<string> (to compare two strings) taking the returned name, and comparing it to "Nick".
bind2nd binds the word "Nick" as the 2nd argument to equal_to.
Finally, compose1 takes two functions, it calls the second function first (ie. GetName) and then passes the result of it to the first function (equal_to).
The net result is that we compare each name in the vector and find a result.
Now to demonstrate how it can be more generally used the next line finds by a different criteria ...
ShowDetails (find_if (v.begin (), v.end (),
compose1 (bind2nd (equal_to<int> (), 88),
mem_fun_ref ( &player::GetAge ))
));
This uses the same general idea, but now it calls GetAge to find the player's age, and equal_to<int> to do the comparison.
Finally the remove_copy_if line demonstrates how you can apply a test over a range, and display the results:
remove_copy_if (v.begin(), v.end(),
ostream_iterator<player>(cout),
not1 (
compose1 (bind2nd (greater<int> (), 25),
mem_fun_ref ( &player::GetAge ))
)
);
This time we are looking for ages greater than 25, hence the function greater<int> is used. However because remove_copy_if copies everyone *except* those matching, we need to negate the sense of the test by throwing in another function adapter - not1. This negates a unary argument.
Have fun playing with this - I hope it is helpful to someone, as it took a lot of work to research. :)
// disable warnings about long names
#ifdef WIN32
#pragma warning( disable : 4786)
#else
#include <iostream> // seems to clash on Windows
#endif
#include <string>
#include <algorithm>
#include <functional>
#include <iterator>
#include <vector>
using namespace std;
// SGI extension for Linux etc.
#ifndef WIN32
// unary_compose (extension, not part of the standard).
template <class _Operation1, class _Operation2>
class unary_compose
: public unary_function<typename _Operation2::argument_type,
typename _Operation1::result_type>
{
protected:
_Operation1 _M_fn1;
_Operation2 _M_fn2;
public:
unary_compose(const _Operation1& __x, const _Operation2& __y)
: _M_fn1(__x), _M_fn2(__y) {}
typename _Operation1::result_type
operator()(const typename _Operation2::argument_type& __x) const {
return _M_fn1(_M_fn2(__x));
}
};
template <class _Operation1, class _Operation2>
inline unary_compose<_Operation1,_Operation2>
compose1(const _Operation1& __fn1, const _Operation2& __fn2)
{
return unary_compose<_Operation1,_Operation2>(__fn1, __fn2);
}
#endif
class player
{
public:
player (const string name, const int age)
: m_name (name), m_age (age) {};
// get player details
string GetName () const { return m_name; };
int GetAge () const { return m_age; };
private:
string m_name;
int m_age;
}; // end of class player
// ostream iterator for player class
ostream& operator<< (ostream& os, const player & p)
{
os << p.GetName () << ", age " << p.GetAge () << endl;
return os;
}; // end of ostream& operator<<
vector<player> v;
void ShowDetails (vector<player>::const_iterator i)
{
if (i == v.end ())
cout << "not found" << endl;
else
cout << *i;
} // end of ShowDetails
int main (void)
{
v.push_back (player ("Nick", 12));
v.push_back (player ("Ksilyan", 25));
v.push_back (player ("Meerclar", 88));
v.push_back (player ("Magnum", 42));
// do a search based on the name (using GetName)
ShowDetails (find_if (v.begin (), v.end (),
compose1 (bind2nd (equal_to<string> (), "Nick"),
mem_fun_ref ( &player::GetName ))
));
// do a search based on the age (using GetAge)
ShowDetails (find_if (v.begin (), v.end (),
compose1 (bind2nd (equal_to<int> (), 88),
mem_fun_ref ( &player::GetAge ))
));
// display players in a range of ages
cout << "players > 25 years old" << endl;
remove_copy_if (v.begin(), v.end(),
ostream_iterator<player>(cout),
not1 (
compose1 (bind2nd (greater<int> (), 25),
mem_fun_ref ( &player::GetAge ))
)
);
return 0;
} // end of main
Example Output
Nick, age 12
Meerclar, age 88
players > 25 years old
Meerclar, age 88
Magnum, age 42
|
- Nick Gammon
www.gammon.com.au, www.mushclient.com | Top |
|