2

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.

  • Possibly I'm wrong, but I see no reason for `neigh` to be a pointer. If you dont have any particular need, you can declare it as `std::unordered_set neigh` and get rid of destructor. – Gian Paolo Jan 19 '21 at 18:21
  • @GianPaolo I think that then each square inside the ```std::unordered_set``` will have a field of this type and this will be done to infinity theoretically speaking, but a pointer stops this recursive problem. – Mihai Gheoace Jan 19 '21 at 19:22
  • the unordered_set within Square class would be empty by default, you would not have the recursive problem you are afraid. Note that you are creating (with a new) the set in constructor, so if the recursive problem would be real, you would face it with a pointer as well – Gian Paolo Jan 19 '21 at 19:59
  • Also note that you are breaking the [rule of 3/5/0](https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three): if you need to implement a destructor (and you have to do it for deleting the newed set), you need also to implement copy and move constructor and copy and move assignement operators. If you declare the set not as a pointer but as an instance, this problem disappear: you are in the case of "rule of 0" in this way – Gian Paolo Jan 19 '21 at 20:08

1 Answers1

2

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

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108