简体   繁体   中英

Cannot specialize std::hash to store custom type in unordered_map

I'm trying to use instances of a class defined in a shared library as a key for an unordered_map. But when I specialize the std::hash template for this class called Tile in the regular, right way, for some reason the compiler keeps complaining:

/usr/include/c++/7/bits/hashtable_policy.h:87: error: no match for call to ‘(const std::hash<Tile>) (const Tile&)’
  noexcept(declval<const _Hash&>()(declval<const _Key&>()))>
           ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~

This is the header file in which I define the specialization of std::hash ( pathfinder.h ):

#ifndef PATHFINDER_H
#define PATHFINDER_H

#include <vector>
#include <unordered_set>
#include <unordered_map>
#include <memory>

#include "Libworld/world.h"
#include "node.h"
#include "nodifier.h"
#include "pathgenerator.h"

namespace std {
    template <>
    struct hash < Tile > {
        public:
            size_t operator() ( const Tile & theTile ) const noexcept {
               return ( ( 32768 * theTile.getXPos ( ) ) + theTile.getYPos ( ) );
            }
            size_t operator() ( const Tile * theTile ) const noexcept {
               return ( ( 32768 * theTile->getXPos ( ) ) + theTile->getYPos ( ) );
            }
    };

    template <>
    struct equal_to < Tile > {
        bool operator ( ) ( const Tile & lhs, const Tile & rhs ) const {
            return ( lhs == rhs );
        }
    };
}

class Pathfinder {
    private:
        std::vector < std::unique_ptr < Tile > > theWorld;
        QString theWorldName;
        int worldRows, worldColumns, numberOfEnemies, numberOfHealthPacks;

        std::unordered_set < std::shared_ptr < Node > > worldNodes;
        std::unordered_map < Tile, bool > hasEnemyOrHealthPack;

        std::shared_ptr < Nodifier > worldNodifier;
        std::shared_ptr < PathGenerator > worldPathGenerator;

        Tile startingTile, endingTile;
        std::shared_ptr < Node > startingNode, endingNode;

        // unsigned int findTileWithLocation ( std::vector < std::unique_ptr < Node > > * nodesList, int x, int y ) const;

        void generateNodes ( );
        void generateHasEnemyOrHealthPack ( World worldLoader, int numberOfEnemies, int numberOfHealthPacks );

public:
    Pathfinder ( QString initialWorldName,
                 Tile initialStartingTile,
                 Tile initialEndingTile,
                 std::shared_ptr < Nodifier > initialWorldNodifier,
                 std::shared_ptr < PathGenerator > initialWorldPathGenerator,
                 int initialNumberOfEnemies,
                 int initialNumberOfHealthPacks );

    QString getWorldName ( ) const;
    void loadNewWorld ( const QString & newWorldName );
    void loadNewWorld ( const QString & newWorldName, const Tile & newWorldStartingTile, const Tile & newWorldEndingTile );

    Tile getStartingTile ( ) const;
    void setStartingTile ( const Tile & newStartingTile );
    Tile getEndingTile ( ) const;
    void setEndingTile ( const Tile & newEndingTile );
    std::shared_ptr < Nodifier > getWorldNodifier ( ) const;
    void setWorldNodifier ( const std::shared_ptr < Nodifier > & newWorldNodifier );
    Tile getTileFromWorld ( int x, int y );

    void printNodes ( );
    void printWorld ( );

    std::list < std::pair <int, int> > generatePath ( );
};

#endif

And this is the headerfile of the shared library containing the definition of Tile:

#ifndef WORLD_H
#define WORLD_H

#include "world_global.h"
#include <vector>
#include <memory>
#include <QObject>
#include <QImage>

class Tile
{
public:
    Tile(int xPosition, int yPosition, float tileWeight);
    virtual ~Tile() =default;
    float getValue() const {return value;};
    void setValue(float newValue) {value = newValue;};
    int getXPos() const {return xPos;};
    int getYPos() const {return yPos;};
    void setXPos(int newPos) {xPos = newPos;};
    void setYPos(int newPos) {yPos = newPos;}
    bool operator== (const Tile & other) const
        {return (getXPos() == other.getXPos()) && (getYPos() == other.getYPos());};

protected:
    int xPos;
    int yPos;
    float value;
};

class Enemy : public Tile
{
public:
    Enemy(int xPosition, int yPosition, float strength);
    virtual ~Enemy() = default;
    bool getDefeated() const {return defeated;}
    void setDefeated(bool value) {defeated = value;};

private:
    bool defeated; //false by construction
};

class PEnemy: public QObject, public Enemy
{
    Q_OBJECT
public:
    PEnemy(int xPosition, int yPosition, float strength);
    virtual ~PEnemy() = default;
    float getPoisonLevel() const;
    void setPoisonLevel(float value);

public slots:
    bool poison();

signals:
    void dead();
    void poisonLevelUpdated(int value);

private:
    float poisonLevel;
};

class Protagonist: public QObject, public Tile
{
    Q_OBJECT
public:
    Protagonist();
    void setXPos(int newPos) {xPos = newPos; emit posChanged(xPos, yPos);}
    void setYPos(int newPos) {yPos = newPos; emit posChanged(xPos, yPos);}
    void setPos(int newX, int newY) {if (xPos != newX || yPos != newY) {xPos = newX; yPos = newY; emit posChanged(xPos, yPos);}}
    float getHealth() const {return health;};
    void setHealth(float value) {health = value;}

    float getEnergy() const {return energy;}
    void setEnergy(float value) {energy = value;}

signals:
    void posChanged(int x, int y);

private:
    float health; //100.0f by construction
    float energy; //100.0f by construction
};

class WORLDSHARED_EXPORT World
{
public:
    World() = default;
    //to obtain non-overlapping enemies and healthpacks you should call the following 3 methods in this order
    std::vector<std::unique_ptr<Tile>> createWorld(QString filename);
    std::vector<std::unique_ptr<Enemy>> getEnemies(unsigned int nrOfEnemies);
    std::vector<std::unique_ptr<Tile>> getHealthPacks(unsigned int nrOfPacks);
    std::unique_ptr<Protagonist> getProtagonist();
    int getRows() const {return rows;};
    int getCols() const {return cols;};

private:
    int rows, cols;
    QImage world;
    std::vector<QPoint> used; //only for internal use
};


#endif // WORLD_H

I'm sure I'm doing everything correctly, so I would love if someone could tell me why this doesn't compile?

Thanks very much in advance, I've been stuck on this for way to long.

Best regards, Joshua

If I move the templahttps://stackoverflow.com/help/how-to-answerte specialization into the header file where the Tile class is defined (after the definition of the class of course, but ANYWHERE after that), the code compiles.

Normally, when you also implement the class yourself, this shouldn't matter. I think it has something to do with this class being implemented in a shared library. I'm no C++ guru, so I cannot explain this. But I'd love to here someone still explain it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM