Search code examples
c++setcomparator

Comparator for member variable of type std::set that requires access to other member variables


I have a class ShapeDisplay that stores a set of Rectangles. I would like to store them sorted, therefore I use a std::set. My intention is to provide a custom comparator, which compares the origin (x, y) of the rectangle to a reference point (x, y) in the display.

However, in order to achieve this, the comparator needs access to m_reference. How do I use a custom comparator, that needs access to the class members? Is my design flawed? I know there are newer ways to provide the comparator as in this link, but that doesn't solve my access issue.

Alternatively, I could just have a std::vector that I keep sorted, such that each new Rectangle is inserted in the right position. But since std::set::insert() should do that automatically with a custom comparator, I would prefer that.

Thank you.

struct Point
{
    int x;
    int y;
};

struct Rectangle
{
    int x;
    int y;
    int width;
    int height;
};

class ShapeDisplay
{
    void insertShape(Rectangle rect)
    {
        m_shapes.insert(rect);
    }

    void setReference(Point reference)
    {
        m_reference = reference;
    }

private: 
    struct CenterComparator
    {
        bool operator() (const Rectangle & a, const Rectangle & b) const 
        {
            
            double distA = std::sqrt(std::pow(a.x - m_reference.x, 2) 
                                   + std::pow(a.y - m_reference.y, 2)); 

            double distB = std::sqrt(std::pow(b.x - m_reference.x, 2) 
                                   + std::pow(b.y - m_reference.y, 2)); 

            return distA < distB;
        }
    };

    std::set<Rectangle, CenterComparator> m_shapes;
    Point m_reference;
};

Solution

  • CenterComparator isn't related to ShapeDisplay, it isn't aware of its members and it isn't derived from ShapeDisplay. You need to provide CenterComparator with its own reference Point. You then need to provide an instance of CenterComparator whose reference point is set.

    Note that if you change that comparator's reference point in any way you will break std::set's sorting resulting in Undefined Behavior if you try to use it. So whenever setReference is called, you need to create a new set with a new comparator and copy over the old set.

    Here is your code, adapted with these changes. I assumed you meant setReference and insertShape to be part of the public interface.

    #include <cmath>
    #include <set>
    
    struct Point
    {
        int x;
        int y;
    };
    
    struct Rectangle
    {
        int x;
        int y;
        int width;
        int height;
    };
    
    class ShapeDisplay
    {
    public:
        void insertShape(Rectangle rect)
        {
            m_shapes.insert(rect);
        }
    
        void setReference(Point reference)
        {
            m_reference = reference;
    
            // Create a comparator using this new reference
            auto comparator = CenterComparator{};
            comparator.reference = m_reference;
    
            // Create a new set
            auto new_shapes = std::set<Rectangle, CenterComparator>(
                std::begin(m_shapes), std::end(m_shapes),   // Copy these shapes
                comparator);                                // Use this comparator
    
            m_shapes = std::move(new_shapes);
        }
    
    private: 
        struct CenterComparator
        {
            bool operator() (const Rectangle & a, const Rectangle & b) const 
            {
                
                double distA = std::sqrt(std::pow(a.x - reference.x, 2) 
                                       + std::pow(a.y - reference.y, 2)); 
    
                double distB = std::sqrt(std::pow(b.x - reference.x, 2) 
                                       + std::pow(b.y - reference.y, 2)); 
    
                return distA < distB;
            }
    
            Point reference;
        };
    
        std::set<Rectangle, CenterComparator> m_shapes;
        Point m_reference;
    };