0

Language Version

I am using a version of the GNU C++ compiler that supports C++17 (though I am not yet familiar with all that was added in C++11/14/17).

The Problem

In the code below, I have found that I can insert into a set<> using a custom sorting criterion, but I cannot test for the presence of an element using set<>::find(). The following compilation error results upon a call to set<>::find():

error: no matching function for call to ‘std::setstd::shared_ptr<node_t, node_comp_t>::find(char&) const’
return children.find(c) != children.end();

This is confusing to me since element equivalence is defined by:

!(lhs < rhs) && !(rhs < lhs)

As can be seen below, I have the required operator defined.

Undesirable Alternative

As an alternative, I can search through the set<> element-by-element, but that is not preferable since doing so would run in linear time.

My Questions

  1. What am I misunderstanding?
  2. Is there a way I can search in logarithmic time, as one normally could with set<>::find()?

Code

struct node_t;
using node_sp_t = shared_ptr<node_t>;

class node_comp_t
{
   public:
      inline bool operator()(const node_sp_t &lhs, const node_sp_t &rhs) const;
};

using node_sp_set_t = set<node_sp_t, node_comp_t>;

class node_t
{
   public:
      explicit node_t(char c_p): c{c_p} {}

      inline char get_c() const
      {
         return c;
      }

      void add_child(char c)
      {
         if (!child_exists(c))
            children.insert(make_shared<node_t>(c));
      }

      bool child_exists(char c) const
      {
         // c is not of the children set element type (node_sp_t).
         // So, find() cannot be used with c.
         //
         // Why does node_comp_t not get used by find()?
         //
         // How may I search for c in logarithmic time if not by using find()?
         //
         return children.find(c) != children.end();

         // This works, but it runs in linear time!
         // for (const node_sp_t &node_sp : children)
         // {
         //    if (node_sp->get_c() == c)
         //       return true;
         // }

         // return false;
      }

   private:
      char c;
      node_sp_set_t children;
};

inline bool node_comp_t::operator()(const node_sp_t &lhs, const node_sp_t &rhs) const
{
   return lhs->get_c() < rhs->get_c();
}
Dave
  • 1,519
  • 2
  • 18
  • 39
  • Did you try `children.find(node_t{c})` (also it seems like you might want a transparent comparator) – Artyer Mar 16 '22 at 01:18
  • I don't believe children.find(node_t{c}) would work. We'd still not be calling find() what it wants to be called with, which is a node_sp_t. We would instead be calling it with a node_t. – Dave Mar 16 '22 at 03:05

1 Answers1

1

Your comparator takes two arguments of type const node_sp_t&, so you must have a node_sp_t object to compare against.

In C++14, you can use a transparent comparator, allowing you to compare against a different type (in this case, char)

class node_comp_t
{
   public:
      inline bool operator()(char lhs, const node_sp_t &rhs) const;
      inline bool operator()(const node_sp_t &lhs, char rhs) const;
      inline bool operator()(const node_sp_t &lhs, const node_sp_t &rhs) const;
      using is_transparent = void;
};

inline bool node_comp_t::operator()(char lhs, const node_sp_t &rhs) const
{
   return lhs < rhs->get_c();
}
inline bool node_comp_t::operator()(const node_sp_t &lhs, char rhs) const
{
   return lhs->get_c() < rhs;
}
inline bool node_comp_t::operator()(const node_sp_t &lhs, const node_sp_t &rhs) const
{
   return lhs->get_c() < rhs->get_c();
}

But since you are using C++11, this isn't an option with std::set (you can use this or something like it with alternative implementations, like boost::container::set).

An "easy" way to create a shared_ptr for cheap without dynamically allocating is to use the aliasing constructor:

bool child_exists(char c) const
{
    node_t compare_against{c};
    return children.find(node_sp_t{node_sp_t{nullptr}, &compare_against}) != children.end();
}

Unfortunately, you have to pay for the cost of constructing a node_t in C++11.

You might also want to move from this in add_child if default constructing is expensive:

void add_child(char c)
{
   node_t compare_against{c};
   if (children.find(node_sp_t{node_sp_t{nullptr}, &compare_against}) == children.end())
      children.insert(std::make_shared<node_t>(std::move(compare_against)));
}

Alternatively, you can use something like std::map<char, node_sp_t> instead.

Artyer
  • 31,034
  • 3
  • 47
  • 75
  • Thank you for this extensive writeup! I will review it carefully in the morning, but for now, I'll note that I do have features through C++17 available to me (e.g., sounds like I'll want to learn about transparent comparators). – Dave Mar 16 '22 at 04:04
  • The transparent comparator did the trick! Thank you. I'll toss out one question that lingers in my mind... Why is it not necessary to have the member function "inline bool node_comp_t::operator()(char lhs, char rhs) const {return lhs < rhs;}" – Dave Mar 16 '22 at 12:02
  • 1
    @Dave Because you never compare a char against a char (when you do `children.find(c)`, you compare `char c` against the `node_sp_t` objects in the set, not other chars) – Artyer Mar 16 '22 at 16:55