Question

specifically I have a list of objects of a class with multiple string object data members(NID, customerNumber, studentNumber, fName, lName).

I want to reuse the following code to search for the node that matches the search key whether the data member that is looked for is NID or any other of the class's string data members.

nodePtr = firstPtr;
for(; nodePtr != NULL && nodePtr->str != str; nodePtr = nodePtr->nextPtr);

if(nodePtr != NULL)
    //the nodePtr points to the node that matches the search key
else
    //no node matched the search key

if it was PHP code I could use the value of a variable as the name of another:

$node->${$var}

but in C++ is there anyway to reuse the code?

Was it helpful?

Solution

The most flexible way to do this is to provide a predicate as a template parameter:

template <typename Pred>
Node * find_if(Node * node, Pred pred) {
    for (; node && !pred(node); node = node->next);
    return node;
}

In C++11, you can call it with a lambda:

if (Node * node = find_if(first, [&](Node * n){return n->NID == nid;})) {
    // node points to the matching node
} else {
    // not found
}

or, if you're stuck in ages past, a function object:

struct CompareNID {
    CompareNID(std::string nid) : nid(nid) {}
    bool operator() {Node * n) {return n->NID == nid;}

    std::string nid;
};

Node * node = find_if(first, CompareNID(nid));

or, since all your fields are strings, you could sacrifice flexibility for tersity using member pointers, giving something similar to your PHP example:

Node * find(Node * node, std::string Node::*member, std::string const & value) {
    for (; node && node->*member != value; node = node->next);
    return node;
}

Node * node = find(first, &Node::NID, nid);

OTHER TIPS

Similar to std::find_if:

template<typename N, typename P>
N* my_find_if(const N* head, P pred)
{
    N* ptr;
    for (ptr = head; ptr != nullptr && !pred(ptr); ptr = ptr->nextPtr)
        ;

    return ptr;
}

Can be called like this:

my_find_if(firstPtr,
    [](Node* node){ return node->str == str; });

Change the lambda to whatever expression you need.

Of course, I would rather recommend you to use the standard containers instead of creating your own list. Then you could use the standard std::find_if instead.

Yes, you want to pass in 2 lambdas (C++11) or runnable objects (C++03) into your "find" algorithm.

With C++03 you might pass in 2 boost functions, one for the "found" case and one for the "not found" case.

void search( std::string str, 
            boost::function<void()> ifFound, 
           boost::function<void()> ifNotFound )
{
      //search
     if( nodePtr != NULL )
     {
          ifFound();
     }
     else
     {
          ifNotFound();
     }
}

Choose your function signatures to match, but that's how you pass in the dynamic functions.

You could use std::function instead of boost::function.

If it's the searching itself you want to make flexible, i.e. what part of your object you are trying to match, use also a dynamic predicate.

void search( Pred pred// , ifFound, ,ifNotFound )
{
     if( pred( nodePtr ) ) // then it is found
}

The predicate, as you see, will take a node-pointer and will return true/false. Thus different predicates would be used to match different data members.

If you really like the concept of "reusable code" though, I would suggest you use the standard library.

If your lists are long and you are continually doing these searches, manual search is slow and you could use a boost::multi_index to create log-N time look-ups on your various fields.

Maybe you want to use pointer-to-members:

typedef string Node::*NodeStringPtr;
NodeStringPtr nodeStrPtr = nullptr;
std::vector<NodeStringPtr> nodeStrings {&Node::NID, &Node::str, &Node::fName};
for (auto& ptr : nodeStrings)
{
  nodePtr = firstPtr;
  for (; nodePtr != NULL && nodePtr->*ptr != str; nodePtr = nodePtr->nextPtr);
  if (nodePtr)
  {
    nodeStrPtr = ptr;
    break;
  }
}

if(nodePtr != NULL)
    //the nodePtr matches the search key, nodeStrPtr matches the element
else
    /* ...*/

One other way is to use pointers to members (only possible if all the members are of the same type):

struct Item {
    std::string NID,
                customerNumber,
                studentNumber,
                fName,
                lName;
};

typedef std::vector<Item>::iterator nodePtr;

typedef std::string Item::* MemberPtr;

struct List {
    std::vector<Item> list;

    nodePtr search(const std::string& str, MemberPtr mem_ptr)
    {
        // this is the code you want to reuse, I took the liberty and used
        // standard lib's algorithm
        return std::find_if(list.begin(), list.end(),
                            [&](const Item& item){ return str == item.*mem_ptr; });
        // will return list.end() if it doesn't find anything
    }
};

int main()
{
    List lst;
    lst.search("John", &Item::fName);
    lst.search("Doe", &Item::lName);
    lst.search("42", &Item::customerNumber);
}

I think this is the closest you can get to your PHP example.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top