Matthew Vanlandingham
Matthew Vanlandingham

Reputation: 646

How do I establish a comparator for a set c++

I have a set, and for this set, I need two different comparators. For example, for a set frontier I need to sort by cost, but I have another set board which needs to be sorted by coordinates. I know you can define a comparator for each set using the comparator as the second argument, but I have tried this and it gave me an error.

The code I tried to use:

struct tile {
int id;
int xCord;
int yCord;
int cost;

...

bool operator<(const tile& Rhs) const {

    if (cost < Rhs.cost) {
        return true;
    }
    else if (cost < Rhs.cost) {
        return false;
    }
    else {
        if (id < Rhs.id) {
            return true;
        }
        else
            return false;
    }


}

...


};

The other struct that I'm using for the comparator (I know this is most likely incorrect, which is why I'm asking for help.):

struct costComp {
int id;
int xCord;
int yCord;
int cost;

costComp() {}

costComp(int a, int b, int c, int d = 0) :
    id(a),
    xCord(b),
    yCord(c),
    cost(d) {}


bool operator<( const tile& Rhs) const {
    if (xCord < Rhs.xCord)
        return true;
    else if (xCord < Rhs.xCord)
        return false;
    else {
        if (yCord < Rhs.yCord)
            return true;
        else if (yCord < Rhs.yCord)
            return false;
        else
            return false;
    }
}
};

Then, I define the set as:

set<tile,costComp> startBoard;

The error I got:

c2064: term does not evaluate to a function taking 2 arguments

Any help is greatly appreciated.

Upvotes: 1

Views: 954

Answers (1)

alter_igel
alter_igel

Reputation: 7212

the Compare parameter in std::set is intended to be some callable type that can be invoked with (const tile&, const tile&). This means you can use a functor that overloads operator(), for example, like this:

struct Comp {
    bool operator()(const tile& lhs, const tile& rhs) const {
        if (lhs.id < rhs.id) return true;
        if (lhs.id > rhs.id) return false;
        if (lhs.xCord < rhs.xCord) return true;
        if (lhs.xCord > rhs.xCord) return false;
        if (lhs.yCord < rhs.yCord) return true;
        if (lhs.yCord > rhs.yCord) return false;
        return lhs.cost < rhs.cost;
    }
    // or maybe, if this logic already exists:
    bool operator()(const tile& lhs, const tile& rhs) const {
        return lhs < rhs; // invoke tile::operator<(const tile&)
    }
};

...

std::set<tile, Comp> myset;

This way, the comparator struct doesn't need to keep track of the details of any one tile object, and the redundant members of costComp can be removed.


If you want the comparator to be configurable, you can add members to the Comp struct definition and initialize them in a constructor call when you instantiate the set:

struct Comp {
    Comp(bool use_cost = false /* default behavior */) : m_use_cost(use_cost) {}

    bool operator()(const tile& lhs, const tile& rhs) const {
        if (m_use_cost){
            return lhs.cost < rhs.cost;
        } else {
            ...
        }
    }
private:
    const bool m_use_cost;
};

...
// default comparison, won't use cost
std::set<tile, Comp> setA;
// specify custom behaviour
std::set<tile, Comp> setB {Comp{true /* right here */}};

Obviously, the configurability is not limited to one or more bools. It might make sense to have some enum with values like SortByCost, SortByXcoord. Alternatively, you could have a separate functor struct that does each, but this means that sets with different comparators will have different types and will not be inter-copyable or moveable.

Upvotes: 2

Related Questions