c++boostboost-bimap

Finding an element in boost::bimaps::bimap by different type than bimap key type


I have the following code:

#include <boost/bimap/bimap.hpp>
#include <boost/bimap/unordered_multiset_of.hpp>
#include <string>

using namespace boost::bimaps;
using namespace boost;

struct Example
{
    uint64_t id;
};

struct ExampleHash
{
    uint64_t operator()(const Example& item) const
    {
        return item.id;
    }

    uint64_t operator()(const uint64_t item) const
    {
        return item;
    }
};

struct ExampleEq
{
    bool operator()(const Example& l, const Example& r) const
    {
        return l.id == r.id;
    }

    bool operator()(const uint64_t l, const Example& r) const
    {
       return l == r.id;
    }

    bool operator()(const Example& l, const uint64_t r) const
    {
        return operator()(r, l);
    }
};

using BM = bimaps::bimap<
    unordered_multiset_of<std::string>,
    unordered_multiset_of<Example, ExampleHash, ExampleEq>
>;

int main() {
    BM bm;
    bm.insert(BM::value_type("First", Example{1}));

    auto it = bm.right.find(1u);

    return 0;
}

According to boost documentation

template< class CompatibleKey >
iterator find(const CompatibleKey & x);

A type CompatibleKey is said to be a compatible key of (Hash, Pred) if (CompatibleKey, Hash, Pred) is a compatible extension of (Hash, Pred). This implies that Hash and Pred accept arguments of type CompatibleKey, which usually means they have several overloads of their corresponding operator() member functions.

So I thought that auto it = bm.right.find(1u); will work. Unfortunately this generates an compilation error:

error: no match for call to (boost::bimaps::container_adaptor::detail::key_to_base_identity<Example, const Example>) (const long unsigned int&)

My question is if it is even possible to use CompatibleKey of a diffrent type than bimap key type? I have already tried to go over the boost headers, unfortunately the implmentation is too complicated for me to comprehend what is going on.


Solution

  • I agree with your reading that the description seemed to suggest that this usage should be allowed.

    However after long reading and testing, I cannot see how the code would actually support it. What's more, there's this signature:

    template< class CompatibleKey >
      bool replace_key(iterator position, const CompatibleKey & x);
    

    Which according to its docs requires "CompatibleKey can be assigned to key_type". That's a clear contradiction of the "minimal requirements" seen earlier.

    After coming to the conclusion that apparently it can't work, I remembered seeing the same before...:

    WONTFIX In order to deal with compatible keys for hashed indices, you'd need not only transparent equality comparison but also some sort of transparent hash functor such as

    struct generic_hash
    {
      template<typename T>
      std::size_t operator()(const T& x)const
      {
         boost::hash<T> h;
         return h(x);
      }
    };
    

    but using this is tricky (and dangerous):

    multi_index_container<
      std::string,
      indexed_by<
        hashed_unique<identity<std::string>,generic_hash,std::less<void>>
      >
    > c{"hello"};
    
    std::cout<<*(c.find("hello"))<<"\n"; // crash
    

    The reason for the problem is: hashing a std::string does not yield the same value has hashing a const char*, so c.find("hello") does not find the string "hello". This is why ​N3657 applies only to associative containers and has not been extended to unordered associative containers.

    As for std::less<void>, I'm sympathetic to your proposal but would prefer to go in line with the standard, which decided for std::less<void> to be explicitly provided by the user rather than the default.

    I was a little embarrassed to find my own comment from 2014 there :)