6

Scenario

I’ve run into a speedbump while using the STL with what seems like a normal scenario, simplified here:

class Person {
  string Name;
  int    Age;
};

vector<Person> people;
AddPeople(people);

string s("Bob");
find(people.begin(), people.end(), s);

Problem

Unfortunately find wants to compare the entire class.

Question

Is there a better or more appropriate way to do this the “STL way”? The suggested questions weren’t helpful, but I managed to find a couple of related questions but no direct solution.

Work-arounds/Tests

There’s some potential work-arounds:

  1. Forgo find altogether (cluttered, but could be refactored):

    bool bBob = false; for (UINT i = 0; i < people.size(); i++) { if (people[i].Name == s) bBob = true; break; }

  2. Provide conversion operator (implicit conversion doesn’t work; explicit can’t be used in find):

    class Person { string Name; int Age; operator string() {return Name;} };

    Person b ("Bob", 99); string s ("Bob"); b == s; //doesn’t work string(b) == s; //works, but no good for find()

  3. Define a standalone equality operator (simple, effective, but globally exposed):

    BOOL operator==(Person l, string r) { return l.Name == r; }

  4. Define a member equality operator (makes comparison order dependent; object must be first):

    class Person { string Name; int Age; bool operator==(string s) {return Name == s;} };

    Person b ("Bob", 99); string s ("Bob"); b==s; //works s==b; //doesn’t work, but not a problem for find()

It looks like #4 is the best candidate, but none seem ideal or feel “STL”, and some have problems.

toku-sa-n
  • 798
  • 1
  • 8
  • 27
Synetech
  • 9,643
  • 9
  • 64
  • 96

3 Answers3

10

Is there a better or more appropriate way to do this the “STL way”?

You can use std::find_if (powered by C++11 lambdas):

std::string name = "Bob";
// ...
std::find_if(std::begin(people), std::end(people), 
    [&] (Person const& p) { return p.Name == name; }

Notice, that calling it "STL way" is inappropriate. This is the C++ Standard Library, not the STL ("Standard Template Library"). The STL served as a strong inspiration for the Containers and Algorithms Library of the C++ Standard Library, but the two things are not the same. See this Q&A on StackOverflow for further information.

EDIT:

Since you are using a compiler that does not support lambdas, you can define your own functor predicate:

struct person_has_name
{
    person_has_name(std::string const& n) : name(n) { }  
    bool operator () (Person const& p) { return p.Name == name; }
private:
    std::string name;
};

And use it with std::find_if this way:

std::string name = "Bob";
// ...
std::find_if(people.begin(), people.end(), person_has_name(name));
Community
  • 1
  • 1
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • `You can use std::find_if (powered by C++11 lambdas).`   Hmm, I just dug out my copy of a C++ book and sure enough it does (briefly) mention `find_if`. I’ll look into that (hopefully VS2003 supports it). Thanks for the information about STL≠C++SL, I resisted STL for a long time and stuck to MFC, so I’m not familiar with the distinctions. – Synetech May 08 '13 at 16:30
  • @Synetech: Unfortunately, VS2003 does not support it. You would need VS2010 at least. Otherwise, you can define your own functor – Andy Prowl May 08 '13 at 16:32
  • Ah, okay. I have a book from 2005 that mentions `find_if`, so I figured it was from C99; I’m fine with using it with a functor. – Synetech May 08 '13 at 16:36
  • @Synetech: I updated the answer, this should work with VS2003 – Andy Prowl May 08 '13 at 16:36
1

There are a couple of ways to do it, all involving some kind of callable objects and std::find_if.

The first is to use the new C++11 lambda:

std::find_if(people.begin(), people.end(), [](const Person& person)
    { return person.Name == "Bob"; });

If you have an older compiler that doesn't support lambdas, you could use a functor object:

class FindPersonByName
{
    std::string name;

public:
    FindPersonByName(const std::string& name) : name(name) {}

    bool operator()(const Person& person) const
        { return person.Name == name; }
};

std::find_if(people.begin(), people.end(), FindPersonByName("Bob"));

Of course both of these requires your class to have the Name member public. But you can change it to use a public GetName function instead and add that to the class.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • That looks promising. Using arrays (even simulated arrays) feels “old fashioned” and doesn’t fit the “container way”. I’ll give both a shot. – Synetech May 08 '13 at 16:34
  • Isn’t there a way to use a callback instead of a lambda? Something like: `bool CompName(Person p, string s) {return p.Name==s;} … find_if(people.begin(), people.end(), "Bob", CompName);` Hmm, it looks like a [adapter might work](http://www.cplusplus.com/forum/beginner/393/#msg1301). – Synetech May 08 '13 at 17:04
  • @Synetech The C++11 way is to use [`std::bind`](http://en.cppreference.com/w/cpp/utility/functional/bind), but since you seem to have an old compiler then one of the (now) deprecated [function adaptors](http://en.cppreference.com/w/cpp/utility/functional) would have to do. – Some programmer dude May 09 '13 at 00:34
1

I guess you want these for subclasses or classes that share a property called name, if not you could create a simple function yourself. If you do want it to work for any class that has a property name you could create a template like this:

template < class ClassWithNameProperty >
ClassWithNameProperty * searchName (std::string name,
                                    std::vector<ClassWithNameProperty *> array)
{
    for (ClassWithNameProperty * obj: array)
    {
        if (obj.compare(obj->name))
        {
            return obj;
        }
    }
    return NULL;
}

Or however you want your search function to work, hope this helps you

heczaco
  • 198
  • 1
  • 13