Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why no default hash for C++ POD structs?

I want to use a POD struct as a hash key in a map, e.g.

struct A { int x; int y; };
std::unordered_map<A, int> my_map;

but I can't do this, since no hash function is auto-generatable for such structs.

  • Why does the C++ standard not require a default hash for a POD struct?
  • Why do compilers (specifically, GCC 4.x / 5.x) offer such a hash, even if the standard doesn't mandate one?
  • How can I generate a hash function, using a template, in a portable way, for all of my POD structures (I'm willing to make semantic assumptions if necessary)?
like image 705
einpoklum Avatar asked Mar 06 '16 11:03

einpoklum


2 Answers

As from the documentation, a possible implementation in your case would be:

#include<functional>
#include<unordered_map>

struct A { int x; int y; };

namespace std
{
    template<> struct hash<A>
    {
        using argument_type = A;
        using result_type = std::size_t;
        result_type operator()(argument_type const& a) const
        {
            result_type const h1 ( std::hash<int>()(a.x) );
            result_type const h2 ( std::hash<int>()(a.y) );
            return h1 ^ (h2 << 1);
        }
    };
}

int main() {
    std::unordered_map<A, int> my_map;
}

The compiler us not allowed to generate such a specialization because of the standard that does not define anything like that (as already mentioned in the comments).

like image 151
skypjack Avatar answered Oct 19 '22 04:10

skypjack


There is a method to generate hash for POD, like good old c style. Only for real POD with no any linked data on the outside of struct. There is no checking of this requirements in code so use it only when you know and can guarantee this. All fields must be initialized (for example by default constructor like this A(), B() etc).

#pragma pack(push)  /* push current alignment to stack */
#pragma pack(1)     /* set alignment to 1 byte boundary */
    struct A { int x; int y; };
    struct B { int x; char ch[8] };
#pragma pack(pop)   /* restore original alignment from stack */

struct C { int x __attribute__((packed)); };


template<class T> class PodHash;

template<>
class PodHash<A> {
public:
    size_t operator()(const A &a) const
    {
        // it is possible to write hash func here char by char without using std::string
        const std::string str =
            std::string( reinterpret_cast<const std::string::value_type*>( &a ), sizeof(A) );
        return std::hash<std::string>()( str );
    }
};

std::unordered_map< A, int, PodHash<A> > m_mapMyMapA;
std::unordered_map< B, int, PodHash<B> > m_mapMyMapB;

UPD: Data structure must be defined in data packing section with value of one byte or with pack attribute for prevent padding bytes.

UPD: But I need to warn that replace deafult packing will make data loading/storing from/to memory for some fields little slowly, to prevent this need to arrange structure data fields with granularity that corresponding your (or most popular) architecture.

I suggest that you can add by yourself additional unused fields not for using but for arrange fields in your data structure for best prformance of memory loading/storing. Example:

struct A
{
    char x;           // 1 byte
    char padding1[3]; // 3 byte for the following 'int'
    int y;            // 4 bytes - largest structure member
    short z;          // 2 byte
    char padding2[2]; // 2 bytes to make total size of the structure 12 bytes
};

#pragma pack is supported by, at least:

  • Microsoft compiler
  • GNU compiler (webarchive)
  • clang-llvm compiler (webarchive)
  • Embarcadero (Borland) compiler (webarchive)
  • Sun WorkShop Compiler (webarchive)
  • Intel compiler is compatible with GCC, CLANG and Microsoft compiler
like image 1
oklas Avatar answered Oct 19 '22 05:10

oklas