smart ordering definition (2nd part)
- By Giuseppe Puoti
- dom 26 ottobre 2014
Here I am to honor the promise I've done closing my last article to explain (I mean even to myself) more tricks to write better ordering composition. What I had in mind closing the article was about situations where containers actually contains pointers to our objects. Say, for example, you want to order something like:
vector<Person*> people;
people.push_back(new Person("Jill", "Evans", 29));
people.push_back(new Person("Jill", "Evans", 23));
people.push_back(new Person("Jill", "Doe", 31));
people.push_back(new Person("Joe", "Doe", 34));
people.push_back(new Person("Alan", "Doe", 68));
// this will not work!
sort(people.begin(), people.end(), SmartOrderBy<Person::OrderBySurname, Person::OrderByName>());
The code above do not compile because SamartOrderBy, as defined last time, will pretend to call the two component ordering functions using pointer to Person ant that overloads are not defined.
As usually in engineering there are more then one possible solution, let discus a couple of them.
first solution
What if we change the SmartOrderBy struct to have an overloaded operator() to accept pointer?
template <class Ord1, class Ord2>
struct SmartOrderBy{
private:
Ord1 o1;
Ord2 o2;
public:
template <typename Obj>
bool operator() (const Obj& obj1, const Obj& obj2){
if(!o1(obj1, obj2) && !o1(obj2, obj1) ){
return o2(obj1, obj2);
}
return o1(obj1, obj2);
}
template <typename Obj>
bool operator() (const Obj* pObj1, const Obj* pObj2){
const Obj& obj1 = *pObj1;
const Obj& obj2 = *pObj2;
if(!o1(obj1, obj2) && !o1(obj2, obj1) ){
return o2(obj1, obj2);
}
return o1(obj1, obj2);
}
};
This will not work well because when we call the operator() using pointers as parameters, the compiler create an implementation of the first operator() with Obj = Person* so that the pointer to reference conversion do not take place.
At first time i thought what i wrote was no really an overload because of the not common understanding of the type parameter. As someone noticed on a reddit comment, I was wrong. The compiler actually generate operator() as an overloaded function but, because my test code used a container filled with non const Person pointers it preferes:
operator()(Person* const&, Person* const&)
over
operator()(const Person*, const Person*)
because of the conversion to const pointer. Writing the pointer version as non const seems to solve. Even in case you use a container containing const pointer to object because, in this case the compiler yield a version of the operator using, for example, Obj = const Person*
template <class Ord1, class Ord2>
struct SmartOrderBy{
private:
Ord1 o1;
Ord2 o2;
public:
template<typename Obj>
bool operator() (const Obj& obj1, const Obj& obj2){
if(!o1(obj1, obj2) && !o1(obj2, obj1) ){
return o2(obj1, obj2);
}
return o1(obj1, obj2);
}
template<typename Obj>
bool operator() (Obj* pObj1, Obj* pObj2){
return operator()(*pObj1, *pObj2);
}
};
The correction (and appreciated lesson) I've received on reddit, makes this solution quite attractive to me. It is simple to implement and to use with practically no verbosity overhead.
another possible solution
One different approach can be wrap the functor with another whose only responsibility is to adapt the incoming parameters to be acceptable to the wrapped functor. It is really simply implemented like:
template <class Func>
struct AcceptPointer {
Func f;
template <typename T>
bool operator() (const T x, const T y){
return f(*x, *y);
}
};
void main(){
vector<Person*> people;
...
sort( \
people.begin(), \
people.end(), \
AcceptPointer <SmartOrderBy<Person::OrderBySurname, Person::OrderByName> >() \
);
}
Also here the solution come at cost of little verbosity in use but, in exchange, we have the possibility to use it with any binary predicate to let it accept pointers as input. In case of nesting SmartOrderBy functors, there is no additional verbosity cost, because the composite functor is adapted by the most external wrapper as a whole.
Even more, typedefs come to help use so, to me, the cost for this solution is not so expensive.
conclusions
I find this last option also a good one, I know there is also some better one, but them are probably outside my current capacity. Will try to implement another solution based on tag dispatch based on pointerness as suggested by comments on reddit. Hope this will help you to better understand template meta-programming and find its convenient everyday programming use.