A convenient approch for feature extractor

When I've start seriously using STL algorithms, I suddenly realized how much I miss a way to define anonymous class or functions. For example, it is a quite common need to run an STL algorithm based on some of the prooperty of object a container is holding. In this case, you should have defined an unarary functor for each of the ordering methods and, of couse, every time you need another, another functor shall be written.

Lambda functions have beed introduced in C++11 and i find that a conveniente and expressive solution but still you could not use C++11 (as I do) or you may want to not repeat yourself each time you call an STL algorithm to specify the functor to use as a lambda.

I've written some article using ordering as my use case but, as the helper infrastructure I'm going to describe apply in many different cases, this time I'll use a search use case.

Consider a situation with a container filled with a number of objects of type A defined as:

class A {
  int i;
  string s;

public:
  A(int i, string s){
    this->i = i;
    this->s = s;
  }

  string getName  () const { return s; }
  void setName(string s){ this->s = s; }

  int getID() const { return i; }
  void setID(int n){ i = n; }
};

int main(int argc, char* argv[]){
  vector<A> v;

  A a(2, "foo");
  A b(7, "bar");
  A c(2, "foo");

  v.push_back(a);
  v.push_back(b);
  v.push_back(c);
  ...

What if you want to find the first element in v whose name is foo ?

My naive default approch

My naive approch are actually two depending of the generality of the equality criteria I'm about to implement. If the criteria is common enough, I can expand the A class with an inner class defining the equality criteria such as:

class A {

  ...

  public:
      struct EqualName {

        EqualName(string target){
          t = target;
        }

        bool operator() (A& a){
          return a.getName() == t;
        }

        private:
          string t;
      };
};

int main(int argc, char* argv[]){
  ...
  vector<A>::const_iterator x = find_if(v.begin(), v.end(), A::EqualName("foo") );

  if(x == v.end())
    cout << "not found" << endl;

  ...
}

If the criteria is less common and think I'll not use it in other places, I write the struct locally, for example in the main function.

To be honest I dislike this solutions, thinking they are good only to manage much more particular property matching. So I've spent some time to find a better solution to better use the fact that my match is simply based on an object property.

A better approch

What STL requires

In the example, but this apply to much more algorithms say, for example, std::copy_if or the definition of std:set, what STL requires is a:

  • Range on a container like object (ok I've it!)
  • Boolean functor (or even a simple function) on the elements of the container.

Here is a schematization of what find_if behaves, highlighted in blue the matcher functor we must provide.

I've implemented it with the naive approch but I could have noticed something extra. I mean, to provide a matcher functor to check the property what I need is just something able to extract the property and proxy the operation. And this is what the functor implemented with the naive approch is: it is a function that:

  1. extract the name property
  2. compare the result with the target

If you name:

n
the method that extract the name from the A object
eq
the compare operation on strings

what the naive approch implement is a composition of n and eq fixing a second string for the comparator.

Here is a schematization of what the naive approch is

A better approch would be write a matcher functor parametrized on the kind of preprocessing to apply before the actual compatation. In better terms what i'd like is: a matcher functor that can adapt its input before doing its compare operation.

Even better, the operation on adapted value itself can be parametrized so that it could be an equality or a ordering or something else. So, the matcher I'm about to write shall:

  • Adapt its input by a user defined adaptor
  • Fix a target property value
  • Use the adaptor result and the fixed property value to feed user defined existing operation.

I've named this functor Has because it checks if an object has a particular property value. The picture below try to depict what Has schematically looks like.

The Has functor schematically. Red circles are template functions.

And here is the code, it is quite simple too!

template <typename OP, typename AdaptorT, typename T>
struct Has{
  const T& t;
  AdaptorT& adapt;
  const OP& op;

  Has(const OP& oper, AdaptorT& adaptor, const T& target) : op(oper), t(target), adapt(adaptor){
  }

  Has(const Has<OP, AdaptorT, T>& other): op(other.op), t(other.t), adapt(other.adapt){
  }

  template <typename ObjT>
  bool operator() (const ObjT& a) const {
    T x = adapt(a);

    return op(x, t);
  }
};

template <typename R, typename AdaptorT, typename OP>
Has< OP, AdaptorT, R> has( AdaptorT& adaptor, const OP& op, const R& target){
  return Has< OP, AdaptorT, R>(op, adaptor, target);
}

The additional template function is in place only to not specify all template parameters every each time you need to create a matcher object. Say you have a functor that retrieve the name from A objects using the A::getName method, to find objects named foo you can write something like:

int main(int argc, char* argv[]){

  ...

  struct NameExtractor {
    string operator() (const A& a){
      return a.getName();
    }
  };

  vector<A>::const_iterator x = std::find_if(v.begin(), v.end(), has(NameExtractor(), std::equal_to<string>(), string("foo")));
  ...
}

Not enough

As I'm a lazy programmer, Has itself is still not enough. In fact it require to write an attribute extractor for every attribute you want to adapt to. What we still need is a way to automatically implement that kind of adaptors. If you was thinking to preprocessor macro, forget it, i'm not going to use macro as a morre expressive and safer solution is available using template metaprogramming.

What I'm going to write is a template functor that will use any method to create and adaptor of the object on witch the method is defined to the type of the method return value.

I called this adaptor generator Prop because it extract properties and here is how it looks like.

template <typename R, typename T, R (T::*getMethod) () const >
struct Prop {

  R operator() (const T& o) const {
    return (o.*getMethod)();
  }

};

The template parameters representes:

R
the return type of the method we want to use as an adaptor. That is the type we want to obtain as the adaption result.
T
the type of object we want to adapt
R (T::*getMethod) () const
the method of T we want to use as an adaptor. Of course it must return an R and accept empty parameter list.

Once we have Prop in place, it makes really simple to write again the initial find operation like:

int main(int argc, char* argv[]){
  vector<A> v;
  A a(2, "bar");
  A b(7, "foo");
  A c(2, "foo");

  v.push_back(a);
  v.push_back(b);
  v.push_back(c);

  typedef Prop<string, A, &A::getName> NameRetrieverT;
  NameRetrieverT name;

  vector<A>::const_iterator x = find_if(
    v.begin(), v.end(),
    has(name, equal_to<string>(), string("foo") )
  );

  return x != v.end();
}

Apart from the expressiveness of the result it is simply reusable. Say, for example you want the first A with ID 7. All you must say to the compiler is that you want to use a different method:

int main(int argc, char* argv[]){

  ...

  typedef Prop<int, A, &A::getID> IDRetrieverT;
  IDRetrieverT id;

  vector<A>::const_iterator x = find_if(
    v.begin(), v.end(),
    has(id, equal_to<int>(), 2)
  );

  return x != v.end();

}

Even better you can change the evaluation criteria of the property. For example one may want to search for the first object that has its name lexicographically minor to foo. Here is how you can simly code it once the has function and the property extractor template adaptor are in place:

int main(int argc, char* argv[]){

  ...

  typedef Prop<string, A, &A::getName> NameRetrieverT;
  NameRetrieverT name;

  vector<A>::const_iterator x = find_if(
    v.begin(), v.end(),
    has(name, less<string>(), string("foo")
  );

  return x != v.end();

}

Even if I'm not inventing nothing so new to real guru, I hope that this article can explain something new to programmers that want to learn use C++ at its full power like I am.