In this article we will discuss how to use std::unordered_set with User defined classes.

std::unordered_set internally implements a hash table to store the elements. To know more about Hash Table visit the following article, What is Hashing and Hash Table?

std::unordered_set uses the hasher and comparison function. For primitive data types like int, string etc. default hasher anc comparision functions will work i.e.

  • std::hash<T>()
  • std::equal_to<T>

But for User defined Objects, these default implementations will not work. We need to provide some extra to make this default functions work.

There are 2 ways to make an unordered_set of User Define Types / Classes i.e.

  • Create special functions to make default std::hash<> & std::equals_to<> functions to work with User Defined classes
  • Creating Custom Hasher and Comparision Functors and pass it to unordered_set.

Lets see them one by one. But, first create a Student class i.e.

class Student
{
  int mId;
  std::string mName;

public:
  Student(int id, std::string name) :
      mId(id), mName(name)
  {
  }
  void
  displayInfo()
  {
    std::cout << mId << " :: " << mName << std::endl;
  }
  bool
  operator ==(const Student & obj) const
  {
    if (mId == obj.mId)
      return true;
    else
      return false;
  }

  int
  getId() const
  {
    return mId;
  }
  std::string
  getName() const
  {
    return mName;
  }
};

Unordered_set of User Defined Class with default Hasher & Comparision Function

Lets create an unordered_set of type Student taht should store unique Student objects based on ID.

// Declaring unordered_set of Student
  std::unordered_set<Student> setOfStudents;

Here, we have not provided any Custom Hasher or Comparision function hence default hasher and comparison function will be used i.e.

Default Hasher Function for class Student

std::hash<Student>(const Student & obj);

Default Comparison Function for class Student 

std::equals_to<Student>(const Student & obj1,const Student & obj2 );

This will internally class the == operator() function.

So, to make this unordered_set<Student> work, we need to implement the above hasher and == operator for class Student.

Implementing std::hash<Student>,

namespace std
{
  template<>
    struct hash<Student>
    {
      size_t
      operator()(const Student & obj) const
      {
        return hash<int>()(obj.getId());
      }
    };
}

It will compute the hash on only member variable ID for an object of class Student.

Implementing == operator for class Student,

This will compare the ID only to check if two objects of class Student are equal or not i.e.

  bool operator ==(const Student & obj) const
  {
    if (mId == obj.mId)
      return true;
    else
      return false;
  }

Now we can use the std::unordered_set of type Student with default hasher and comparator i.e.
std::unordered_set<Student> setOfStudents;

// Inserting elements
setOfStudents.insert(Student(11, "John"));
setOfStudents.insert(Student(12, "Harry"));
setOfStudents.insert(Student(13, "Ritz"));
setOfStudents.insert(Student(14, "John"));

// Trying to insert with duplicate ID
// It will not be inserted
auto res = setOfStudents.insert(Student(12, "Varun"));

if (res.second == false)
  std::cout << "Failed to insert with ID 12" << std::endl;

The above Unordered Set will store elements with unique size only.Now, suppose a new requirement comes and we also want to store objects in a new unoredered_set with uniwue names instead of unique ID. How to do that?

unordered_set of User Defined Class with Custom Hasher and Comparator

As, now we need to create a new unordered_set of type Student that can store unique Student objects based on name instead of ID, so we can not use the above implementation of std::hash<Student> and operator==.

 

So, to achieve this we can create custom Hasher and Comparator.

Custom Hasher for class Student

It will compute the hash based on name of Student,

// Custom Hash Functor that will compute the hash on the
// passed string objects length
struct StudentHasher
{
  size_t
  operator()(const Student & obj) const
  {
    return std::hash<std::string>()(obj.getName());
  }
};

Custom Comparator for class Student,

It will compare the two Student objects based on name instead of ID i.e.

// Custom comparator that compares the string objects by length
struct StudentComparator
{
  bool
  operator()(const Student & obj1, const Student & obj2) const
  {
    if (obj1.getName() == obj2.getName())
      return true;
    return false;
  }
};

Now lets use these Custom Functors
  // Declaring unordered_set of Student
  std::unordered_set<Student, StudentHasher, StudentComparator> setOfStudByName;

This set will store the unique student objects based on name only.

Complete working Example is as follows,

#include <iostream>
#include <unordered_set>

class Student
{
  int mId;
  std::string mName;

public:
  Student(int id, std::string name) :
      mId(id), mName(name)
  {
  }
  void
  displayInfo()
  {
    std::cout << mId << " :: " << mName << std::endl;
  }
  bool
  operator ==(const Student & obj) const
  {
    if (mId == obj.mId)
      return true;
    else
      return false;
  }

  int
  getId() const
  {
    return mId;
  }
  std::string
  getName() const
  {
    return mName;
  }
};

namespace std
{
  template<>
    struct hash<Student>
    {
      size_t
      operator()(const Student & obj) const
      {
        return hash<int>()(obj.getId());
      }
    };
}

// Custom Hash Functor that will compute the hash on the
// passed string objects length
struct StudentHasher
{
  size_t
  operator()(const Student & obj) const
  {
    return std::hash<std::string>()(obj.getName());
  }
};

// Custom comparator that compares the string objects by length
struct StudentComparator
{
  bool
  operator()(const Student & obj1, const Student & obj2) const
  {
    if (obj1.getName() == obj2.getName())
      return true;
    return false;
  }
};

int
main()
{
  // Declaring unordered_set of Student
  std::unordered_set<Student> setOfStudents;

  std::cout << "Creating an Unordered_set by unique ID\n";

// Inserting elements
  setOfStudents.insert(Student(11, "John"));
  setOfStudents.insert(Student(12, "Harry"));
  setOfStudents.insert(Student(13, "Ritz"));
  setOfStudents.insert(Student(14, "John"));

// Trying to insert with duplicate ID
  auto res = setOfStudents.insert(Student(12, "Varun"));

  if (res.second == false)
    std::cout << "Failed to insert with ID 12" << std::endl;

  for (Student st : setOfStudents)
    st.displayInfo();

  /*-----------------------------------------------*/

  // Declaring unordered_set of Student
  std::unordered_set<Student, StudentHasher, StudentComparator> setOfStudByName;

  std::cout << "Creating an Unordered_set by Unique Name\n";

  // Inserting elements

  setOfStudByName.insert(Student(11, "John"));
  setOfStudByName.insert(Student(12, "Harry"));
  setOfStudByName.insert(Student(13, "Ritz"));

  res = setOfStudByName.insert(Student(14, "John"));
  if (res.second == false)
    std::cout << "Failed to insert with duplicate name - John" << std::endl;

  // Trying to insert with duplicate ID
  res = setOfStudByName.insert(Student(12, "Varun"));

  if (res.second == false)
    std::cout << "Failed to insert with ID 12" << std::endl;
  else
    std::cout << "Successful in inserting with ID 12 again" << std::endl;

  for (Student st : setOfStudByName)
    st.displayInfo();
  return 0;
}

Output:
Creating an Unordered_set by unique ID
Failed to insert with ID 12
14 :: John
13 :: Ritz
12 :: Harry
11 :: John
Creating an Unordered_set by Unique Name
Failed to insert with duplicate name - John
Successful in inserting with ID 12 again
13 :: Ritz
12 :: Harry
12 :: Varun
11 :: John