Search code examples
c++oophashunordered-set

Class with an unordered set of itself


I simplified my question to this. Can I create a class that has an unordered set with template type itself? To be specific, for example a Square that has a pointer to an unordered set of neighbors. I got stuck trying to integrate a hash function with the class itself. Here is my code:

#include <iostream>
#include <unordered_set>

#define SIZE 200
#define MASTER 0
class Square;

namespace std{
    template<>
    struct hash<Square> {
        std::size_t operator () (Square const &v) const
        {
            return v.r;    
        }
    };
}

class Square{
    public:
    int c1, c2;
    int r;
    std::unordered_set<Square> *neigh;

    Square() {
        neigh = new std::unordered_set<Square>();
    }

    ~Square(){
        delete neigh;
    }

    bool operator==(const Square& second) {
        return this->r == second.r 
            && this->c1 ==second.c1
            && this->c2 == second.c2;
    }
};


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

    Square sq;
    Square tt;
    sq.neigh->insert(tt);
}

I tried to compile using g++ and FLAGS = --std=c++17 -Wall -Wextra -Wno-unused-parameter -Wno-unused-variable -ggdb. The error received was gigantic, starting with:

test.cpp: In member function ‘std::size_t std::hash<Square>::operator()(const Square&) const’:
test.cpp:15:20: error: invalid use of incomplete type ‘const class Square’
   15 |             return v.x;

I don't know what is the correct approach to this situation. Please take into consideration this is my simplified code version of what I need, so I really need a neighbors field.


Solution

  • To solve the problem you're asking about, just declare std::hash<Square>::operator() before the Square definition, but don't implement it:

    namespace std{
        template<>
        struct hash<Square> {
            std::size_t operator() (Square const &) const;
        };
    }
    

    Then after the Square definition, define the std::hash<Square>::operator():

    namespace std {
        std::size_t hash<Square>::operator() (Square const& v) const
        {
            // return calculation
        }
    }
    

    You have a problem with the insert too. You copy an object with a pointer and then destroy the same pointer twice. To remedy that, use a std::unique_ptr<std::unordered_set<Square>> which helps since you'll get a compilation error if you try copying it.

    class Square{
    public:
        std::unique_ptr<std::unordered_set<Square>> neigh;
    
        Square() : neigh{std::make_unique<std::unordered_set<Square>>()} {}
    
        // no destructor needed
    
        bool operator==(const Square& second) const { // should be const
            // ...
        }
    };
    

    You then have to move objects into place:

    sq.neigh->insert(std::move(tt));
    

    or emplace them:

    sq.neigh->emplace(...constructor arguments...);
    

    Demo