c++hashdependencies

hash function for incomplete member type


I have a class A, which has an unordered container with instances of a class B, B depends on A, as it has a pointer to its A-instance as a field. I can forward declare B before the implementation of A, but that is insufficient, because the unordered container in A requires the definition of std::hash<B>, which hover cannot be defined before A, because it depends on it.

template <typename X>
class B;

namespace std
{
    
    template <typename V>
    struct hash<B<V>>  // <- requires full definition
    {
        size_t operator()(const B<V>& b) const
        {
           return (b.mem /*do hashing stuff with it*/ );
        }
    };
    
}


template <typename T>
class A
{

typedef A<T> THIS;
    
    void func()
    {
    }
    
    std::unordered_set<B<THIS>>  set;// <- requires std::hash<B>
};


template <typename A>
class B
{
    B(A* a)
    {
        A_ptr = a;
    }
    
    void otherfunc()
    {
        A_ptr->func();
    }
    
public:

    int mem;
    A* A_ptr;
};

Is there a way to resolve this?

B requires A to be complete. A does not require B to be complete, but it requires std::hash<B>, which itself requires B to be complete.

Edit:

I tried to incorporate Richard Hodges proposal into my actual program, but I can't get it to work. This is the order in which the code is compiled in my files:

namespace E
{
    
    template<typename G>
    class R;
    
    template <typename V, typename P>
    class G;
    
}

namespace std
{
    template <typename G> std::size_t hash_code(const E::R<G>&);
    
    
    template <typename G>
    struct hash<E::R<G>>
    {
        size_t operator()(const E::R<G>& r) const
        {
            return hash_code(r);
        }
    };
}

namespace E
{
  
    template <typename V, typename P>
    class G
    {
        // code
    }

    template <typename G>
    class R
    {
        // code
    }

}


namespace std
{
    template<typename G>
    size_t hash_code(const E::R<G>& r)
    {
        size_t hash = 0x9e3779b9;
        
        typename E::R<G>::Rside v = r[0];
        
        for(auto t = v.begin(); t != v.end(); ++t)
        {
            hash += (((*t + (hash << 6)) ^ (hash >> 16)) - hash);
        }
        v = r[1];
        
        for(auto t = v.begin(); t != v.end(); ++t)
        {
            hash += (((*t + (hash << 6)) ^ (hash << 16)) - hash);
        }
        return hash;
        
    }
}

But I am getting

implicit instantiation of undefined template
      'std::__1::hash<std::__1::vector<std::__1::vector<int, std::__1::allocator<int>
      >, std::__1::allocator<std::__1::vector<int, std::__1::allocator<int> > > > >'
    : public integral_constant<bool, __is_empty(_Tp)> {};

and

/usr/include/c++/5.2.0/bits/hashtable_policy.h:85:34: error: no match for call to ‘(const std::hash<std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > > >) (const std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > >&)’
  noexcept(declval<const _Hash&>()(declval<const _Key&>()))>

Solution

  • There are too many errors in your code to provide a working example, but this will get you started.

    There are many ways to do this, but in my view the cleanest is via a free function called hash_code which is found by ADL.

    something like this:

    // forward declarations
    template <typename X> class B;
    template <typename X> std::size_t hash_code(const B<X>&);
    
    // specialisation of std::hash, uses only references so forward declarations are fine.    
    template <typename V>
    struct std::hash<B<V>>  // <- no longer requires full definition
    {
        size_t operator()(const B<V>& b) const {
            return hash_code(b);
        }
    };
    
    ...
    ... later on ...
    ...
    
    // provide the definition of hash_code once B has been defined.
    template<typename A>
    std::size_t hash_code(const B<A>& b) {
        return b.mem;
    }