Question

I'm looking for a way to find an element inside a map using the base class (the code bellow is just a basic example):

#include <map>
#include <boost/shared_ptr.hpp>

class Base {
public:
    Base(int v) : id(v) {};
    int id;  
};

class Derived : public Base {
public:
    Derived(int v) : Base(v) {};
};


int main()
{
    std::map<boost::shared_ptr<Derived>, double> m;
    m.insert(std::make_pair(boost::shared_ptr<Derived>(new Derived(1)), 10));
    m.insert(std::make_pair(boost::shared_ptr<Derived>(new Derived(2)), 20));

    auto b1 = boost::shared_ptr<Base>(new Base(1));
    m.find(b1);

    return 0;
}

Basically, I want to compare the id attribute. The errors returned by the compiler are the following:

main.cpp: In function 'int main()':
main.cpp:35:14: error: no matching function for call to 'std::map<boost::shared_ptr<Derived>, double>::find(boost::shared_ptr<Base>&)'
     m.find(b1);
              ^
main.cpp:35:14: note: candidates are:
In file included from /usr/include/c++/4.8/map:61:0,
                 from main.cpp:1:
/usr/include/c++/4.8/bits/stl_map.h:820:7: note: std::map<_Key, _Tp, _Compare, _Alloc>::iterator std::map<_Key, _Tp, _Compare, _Alloc>::find(const key_type&) [with _Key = boost::shared_ptr<Derived> _Tp = double; _Compare = std::less<boost::shared_ptr<Derived> > _Alloc = std::allocator<std::pair<const boost::shared_ptr<Derived>, double> > std::map<_Key, _Tp, _Compare, _Alloc>::iterator = std::_Rb_tree_iterator<std::pair<const boost::shared_ptr<Derived>, double> > std::map<_Key, _Tp, _Compare, _Alloc>::key_type = boost::shared_ptr<Derived>]
       find(const key_type& __x)
       ^
/usr/include/c++/4.8/bits/stl_map.h:820:7: note:   no known conversion for argument 1 from 'boost::shared_ptr<Base>' to 'const key_type& {aka const boost::shared_ptr<Derived>&}'
/usr/include/c++/4.8/bits/stl_map.h:835:7: note: std::map<_Key, _Tp, _Compare, _Alloc>::const_iterator std::map<_Key, _Tp, _Compare, _Alloc>::find(const key_type&) const [with _Key = boost::shared_ptr<Derived> _Tp = double; _Compare = std::less<boost::shared_ptr<Derived> > _Alloc = std::allocator<std::pair<const boost::shared_ptr<Derived>, double> > std::map<_Key, _Tp, _Compare, _Alloc>::const_iterator = std::_Rb_tree_const_iterator<std::pair<const boost::shared_ptr<Derived>, double> > std::map<_Key, _Tp, _Compare, _Alloc>::key_type = boost::shared_ptr<Derived>]
       find(const key_type& __x) const
       ^
/usr/include/c++/4.8/bits/stl_map.h:835:7: note:   no known conversion for argument 1 from 'boost::shared_ptr<Base>' to 'const key_type& {aka const boost::shared_ptr<Derived>&}'
Was it helpful?

Solution

If you want to use your map for lookup by id, you need to pass in an appropriate comparison function so that the map sorts its keys by id instead of the default operator < (which, I believe, compares ownership block addresses with boost::shared_ptr arguments).

So change the map like this:

struct Less_id
{
  bool operator() (const boost::shared_ptr<Derived> &lhs, const boost::shared_ptr<Derived> &rhs) const
  {
    return lhs->id < rhs->id;
  }
};

typedef std::map<boost::shared_ptr<Derived>, double, Less_id> Map;

Map m;

This will sort the map accordingly, but still not allow lookup by Base pointer. To do that, you can write your own function above std::lower_bound:

Map::const_iterator find_base(const Map &map, const boost::shared_ptr<Base> &base)
{
  auto it = std::lower_bound(
    map.begin(), map.end(), base,
    [](const Map::value_type &lhs, const boost::shared_ptr<Base> &rhs)
    { return lhs.first->id < rhs->id; }
  );
  if (it != map.end() && it->first->id == base->id)
    return it;
  else
    return map.end();
}

std::lower_bound() is used to keep the logarithmic complexity std::map::find() offers.

Live example

OTHER TIPS

Use an

std::map<boost::shared_ptr<Derived>, double, std::less<boost::shared_ptr<Base>>>

Edit: I am ahead of the times - this only works for C++14.

There are two issues to overcome. The first is that you want to search for a Base in a collection of Deriveds. The second is that you want to compare by value rather than by address. The other answers are neglecting this second point. Try std::find_if:

auto b1 = boost::shared_ptr<Base>(new Base(1));
auto itFound = std::find_if(m.begin(), m.end()
   [=](const std::pair<boost::shared_ptr<Derived>, double>& pair)
   {
      // omitting null checks for this example
      return pair.first->id == b1->id;
   });

And if the requirement is really just to find a key with the given id, you could make it simpler:

int queryKey = 1;
auto itFound = std::find_if(m.begin(), m.end()
   [=](const std::pair<boost::shared_ptr<Derived>, double>& pair)
   {
      return pair.first->id == queryKey;
   });

Now, as noted in the comments, this will give you linear rather than map's usual logarithmic lookup time. If the map is small it won't matter, but if this is an issue, you could use std::lower_bound instead of find_if. Note that this would also require adding a custom comparer so you could ensure the map's sort order was based on id. For example:

struct Compare
{
   bool operator()(const boost::shared_ptr<Derived>& l,
      const boost::shared_ptr<Derived>& r) const
   {
      // omitting null checks for this example
      return l->id < r->id;
   }
};

std::map<boost::shared_ptr<Derived>, double, Compare> m;

This is because boost::shared_ptr's operator < is not what you want, you need a delegation to the Derived class's operator <.

use std::map<_Key, _Tp, _Compare>

for example:

 std::map<boost::shared_ptr<Derived>, double, compare_func()> m;

As you are looking for an object Base which can only be in the map if it is of type Derived you can simply do this:

boost::shared_ptr<Derived> d1 = boost::dynamic_pointer_cast<Derived>(b1);
if(d1) {
   m.find(d1);
} else {
   // not in the map
}

As you use std::shared_ptr<Derived> as a key, another possibility would be to actually use pointers to the base class instead:

Use

std::map<boost::shared_ptr<Base>, double> m;

instead of

std::map<boost::shared_ptr<Derived>, double> m;

and everything works as expected.


Btw, you are missing a virtual destructor in Base!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top