Skip to content

Commit

Permalink
Add hash object for enhanced enums
Browse files Browse the repository at this point in the history
  • Loading branch information
jasujm committed Jul 24, 2020
1 parent 9e03b53 commit e9ad343
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Added
- Pylint (test target, configurations)
- Add aliases of ``begin()``, ``end()`` and ``all()`` to the
associate namespace generated by EnumECG
- Add hash object for enhanced enums

Fixed
- Several pylint errors
Expand Down
27 changes: 27 additions & 0 deletions cxx/include/enhanced_enum/enhanced_enum.hh
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,33 @@ constexpr bool operator>=(Enum1 lhs, Enum2 rhs) noexcept

/// \}

/** \brief Hash for enhanced enums
*
* This is a struct template satisfying the requirements of Hash for
* enhanced enum types. Instantiating the template is possibly if and
* only if <tt>\ref is_enhanced_enum_v<Enum> == true</tt>.
*
* \note Due to restrictions of the C++ standard library, the enhanced
* enum library doesn't specialize the \c std::hash template. Please
* see the user guide if you need a specialization of \c std::hash for
* your own type.
*/
template<
typename Enum
#ifndef IS_DOXYGEN
, typename = std::enable_if_t<is_enhanced_enum_v<Enum>>
#endif
>
struct hash
{
/** \brief Calculate hash for \p e
*/
std::size_t operator()(const Enum& e) const noexcept
{
return std::hash<typename Enum::label_type>{}(e.get());
}
};

}

#endif // ENHANCED_ENUM_HH_INCLUDED_
41 changes: 41 additions & 0 deletions cxx/tests/test.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <algorithm>
#include <array>
#include <ostream>
#include <map>

#include <gtest/gtest.h>

Expand Down Expand Up @@ -28,6 +29,13 @@ std::ostream& operator<<(std::ostream& os, const EnumBundle& bundle)

}

namespace std {

template<>
struct hash<EnhancedStatus> : enhanced_enum::hash<EnhancedStatus> {};

}

// Compiling the test executable is already a test:

static_assert( std::is_trivial_v<EnhancedStatus> );
Expand Down Expand Up @@ -187,6 +195,39 @@ TEST_F(EnhancedEnumTest, testIteratorRandomAccessReversal)
EXPECT_EQ(iter[-1], Statuses::BUSY);
}

TEST_F(EnhancedEnumTest, testMap)
{
std::map<EnhancedStatus, StatusLabel> map;
for (const auto e : EnhancedStatus::all()) {
map[e] = e.get();
}
for (const auto e : EnhancedStatus::all()) {
EXPECT_EQ(map.at(e), e.get());
}
}

TEST_F(EnhancedEnumTest, testHash)
{
const enhanced_enum::hash<EnhancedStatus> hasher;
for (const auto e1 : EnhancedStatus::all()) {
for (const auto e2 : EnhancedStatus::all()) {
// I would be _very_ surprised if this failed...
EXPECT_EQ(e1 == e2, hasher(e1) == hasher(e2));
}
}
}

TEST_F(EnhancedEnumTest, testUnorderedMap)
{
std::unordered_map<EnhancedStatus, StatusLabel> map;
for (const auto e : EnhancedStatus::all()) {
map[e] = e.get();
}
for (const auto e : EnhancedStatus::all()) {
EXPECT_EQ(map.at(e), e.get());
}
}

INSTANTIATE_TEST_SUITE_P(
WithEnumBundle,
EnhancedEnumTest,
Expand Down
47 changes: 47 additions & 0 deletions docs/enhancedenumlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,48 @@ the ``EnumECG`` library also contains aliases to the range functions:
Statuses::begin(), Statuses::end(),
[](const auto status) { /* use status */ });
Hashes
......

The library defines a hash template for enhanced enumerations:

.. code-block:: c++

enhanced_enum::hash<EnhancedStatus> hasher;
for (const auto status : EnhancedStatus::all()) {
std::cout << "Hash of " << status.value() << " is " << hasher(status) << "\n";
}

Due to restrictions of specializing the ``std::hash`` template in the
standard library [#]_, the hash object is defined in the
``enhanced_enum`` namespace. But the standard library by default
expects to find the definition of a hash object in the ``std``
namespace. You can do this yourself via inheritance:

.. code-block:: c++

namespace std {
template<>
struct hash<EnhancedStatus> : enhanced_enum::hash<EnhancedStatus> {};
}

std::unordered_map<EnhancedStatus, int> map;
map[Statuses::INITIALIZING] = 123;

Alternatively you can just instantiate the hash template explicitly
when needed:

.. code-block:: c++

std::unordered_map<EnhancedStatus, int, enhanced_enum::hash<EnhancedStatus>> map;
map[Statuses::INITIALIZING] = 123;

.. [#] Because the Enhanced Enum library doesn't know about *your*
types, the C++17 implementation relies on SFINAE to specialize
templates for enhanced enumerations. But the standard library
doesn't allow SFINAE, it only allows partial specializations in
the ``std`` namespace to rely on user defined types explicitly.
Library reference
-----------------

Expand All @@ -355,3 +397,8 @@ Template support

.. doxygengroup:: templatesupport
:members:

Hash
....

.. doxygenstruct:: enhanced_enum::hash

0 comments on commit e9ad343

Please sign in to comment.