why can't i pass two different comparators to one template function?

425 Views Asked by At

I'm wracking my brain here for several hours, but I still don't understand why I'm getting an error when I'm trying to run this code. After some time I managed to narrow it down to the expression:

pastryPrice()

which causes the problem - as you can see, I'm trying to build numerous comparators for one template function of sorting

    struct dialingAreaComp{
    inline bool operator()(const Deliver *d1, const Deliver *d2)const {
        return d1->getDialingArea() < d2->getDialingArea();
    }
};
struct pastryPrice {
    inline bool operator()(const Pastry *p1, const Pastry *p2)const {
        return p1->getPrice() < p2->getPrice();
    }
};
template<class T>
void sortCollection(T& collection)
{
    if ( typeid (collection) == typeid(vector <Deliver*>))
    {
        sort(collection.begin(), collection.end(), dialingAreaComp());
        printCollection(collection);
    }
    else if (typeid (collection) == typeid(vector <Pastry*>))
    {
        sort(collection.begin(), collection.end(), pastryPrice());
        printCollection(collection);
    }
    else { cout << "WRONG!"; }
}

I'm getting five errors, all the same:

Severity Code Description Project File Line Suppression State Error C2664 'bool Bakery::pastryPrice::operator ()(const Pastry *,const Pastry *) const': cannot convert argument 1 from 'Deliver *' to 'const Pastry *' Bakery c:\program files (x86)\microsoft visual studio 14.0\vc\include\xutility 809

And one more:

Severity Code Description Project File Line Suppression State Error C2056 illegal expression Bakery c:\program files (x86)\microsoft visual studio 14.0\vc\include\xutility 809

When I take off the expression I wrote above, the code works just fine - why can't I pass two different comparators to one template function?

Now:

C2264 is a Compiler Error that occurs when one tries to pass a function a parameter of an incompatible type.

But the Deliver function works and when I took off the Deliver comparator the Pastry compiled as well... so what is the incompatible type?

2

There are 2 best solutions below

4
πάντα ῥεῖ On BEST ANSWER

You get an error because the templated function is evaluated at compile time, and one of the function calls will never match. Instead of the template use simple function overloads:

void sortCollection(vector <Deliver*>& collection)
{
    sort(collection.begin(), collection.end(), dialingAreaComp());
    printCollection(collection);
}

void sortCollection(vector <Pastry*>& collection)
{
    sort(collection.begin(), collection.end(), pastryPrice());
    printCollection(collection);
}
8
Yakk - Adam Nevraumont On

Your problem is both branches are compiled regardless of which one is taken.

I would approach this differently.

template<class A, class B>
struct overload_t:A,B{
  using A::operator();
  using B::operator();
  overload_t(A a, B b):A(std::move(a)), B(std::move(b)){}
};
template<class A, class B>
overload_t<A,B> overload( A a, B b ){
  return {std::move(a),std::move(b)};
}

This lets us overload two function objects or lambdas. (Perfect forwarding could be added, as can varargs..., but I kept it simple).

Now we simply:

auto comp=overload(dialingAreaComp{}, pastryPrice{});
using std::begin; using std::end;
std::sort( begin(collection), end(collection), comp );

and the compiler chooses the correct comparison function for us. Also flat array support while I was in there.

And stop using using namespace std;.


What the above code does is fuze your two function object tyoes into one. The using A::operator() and using B::operator() moves both () into the same class and tells C++ to pick between them when invoked using the usual method call overloading rules. The rest of the code is glue to deduce the types being overloaded and move-construct them.

sort calls () with objects of the compile-time determined type based on the type of the container. Overload resolution (within sort at the point of call) then picks the right body to compare at compile time.

Thus technique can be extended with support for more than 2 overloads, function pointers, and forwarding references. In C++17 some work can be done to have the overload type deduce its parent types, removing the need for the factory function.