Skip to content

Instantly share code, notes, and snippets.

@jlschrag
Last active January 20, 2020 20:04
Show Gist options
  • Save jlschrag/e49f0a09ff062226e5c3159c4dabf13a to your computer and use it in GitHub Desktop.
Save jlschrag/e49f0a09ff062226e5c3159c4dabf13a to your computer and use it in GitHub Desktop.

I recently had need for a C++ collection which could be accessed with multiple keys. Specifically, I had an object that I wanted to look up either by name or id (in O(1) time). One way to do this is to create two unordered_maps – one keyed by id and another by name, but that is error-prone. I ended up finding a boost class which will do this. It took me a while get it working, so I thought I would share the result.

//The object being stored in the collection
struct Person
{
int Id;
std::string Name;
};
//A consumer of the collection
class PersonCache
{
private:
PersonMap m_People;
public:
PersonCache();
~PersonCache();
bool IsEmpty() const
{
return m_People.empty();
}
Person* GetPersonByName(std::string lowerCaseName) const
{
auto& indexByName = m_People.get<IndexByName>();
auto iter = indexByName.find(lowerCaseName);
if (iter != indexByName.end()) return &iter.get_node()->value();
return nullptr;
}
Person* GetPersonById(const int id) const
{
auto& indexById = m_People.get<IndexById>();
auto iter = indexById.find(id);
if (iter != indexById.end()) return &iter.get_node()->value();
return nullptr;
}
PersonCache::iterator begin()
{
return m_People.begin();
}
PersonCache::iterator end()
{
return m_People.end();
}
};
//the collection...
//These structs just allow us to name the indexes
struct IndexById {};
/// <summary>
/// This index uses a lower case key extractor, so any lookup against this index should submit a lower case string.
/// </summary>
struct IndexByName {};
/// <summary>
/// When keys are pulled out for comparison, they will be converted to lower case.
/// Built from examples here: http://boost.2283326.n4.nabble.com/multi-index-index-based-on-case-insensitive-strings-td2565253.html
/// That link also provides a more generic, reusable way to do this which is more verbose.
/// </summary>
struct LowerCaseKeyExtractor
{
typedef std::string result_type;
std::string operator()(const Person& person) const
{
return boost::algorithm::to_lower_copy(person.Name);
}
};
//This was implemented using ideas from https://stackoverflow.com/questions/39510143/how-to-use-create-boostmulti-index/39510606#39510606
//There is a good explanation of each part there. See also https://www.boost.org/doc/libs/1_64_0/libs/multi_index/doc/tutorial/basics.html
typedef
boost::multi_index_container<
Person, //The data type stored
boost::multi_index::indexed_by< //List of indexes
boost::multi_index::hashed_unique<
boost::multi_index::tag<IndexById>, //Give the index a name
boost::multi_index::member<Person, int, &Person::Id> //Use Id as the key
>,
boost::multi_index::hashed_non_unique<
boost::multi_index::tag<IndexByName>, //Give the index a name
LowerCaseKeyExtractor
>
>
> PersonMap;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment