aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--doc/src/index.rst30
-rw-r--r--include/newtype/derivable.hpp7
-rw-r--r--include/newtype/impl/type_traits_extensions.hpp38
-rw-r--r--include/newtype/new_type.hpp16
-rw-r--r--test/include/hash_suite.hpp11
-rw-r--r--test/include/kawaii.hpp2
-rw-r--r--test/src/arithmetic_suite.cpp3
-rw-r--r--test/src/driver.cpp2
-rw-r--r--test/src/hash_suite.cpp69
10 files changed, 177 insertions, 2 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8d9d1fe..cbcadd1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -54,6 +54,7 @@ if(BUILD_TESTING)
"${PROJECT_SOURCE_DIR}/test/src/conversion_suite.cpp"
"${PROJECT_SOURCE_DIR}/test/src/derivation_clause_suite.cpp"
"${PROJECT_SOURCE_DIR}/test/src/equality_comparison_suite.cpp"
+ "${PROJECT_SOURCE_DIR}/test/src/hash_suite.cpp"
"${PROJECT_SOURCE_DIR}/test/src/io_operators_suite.cpp"
"${PROJECT_SOURCE_DIR}/test/src/new_type_constructor_suite.cpp"
"${PROJECT_SOURCE_DIR}/test/src/relational_operators_suite.cpp"
diff --git a/doc/src/index.rst b/doc/src/index.rst
index 07f5fc3..133ab7e 100644
--- a/doc/src/index.rst
+++ b/doc/src/index.rst
@@ -1,5 +1,9 @@
.. cpp:namespace-push:: nt
+.. |BaseTypeDoc| replace:: The type of the contained object
+.. |TagTypeDoc| replace:: A tag to uniquely identify an instance of :cpp:class:`nt::new_type`
+.. |DerivationClauseDoc| replace:: A (possibly empty) list of derivation tags as generated by :cpp:func:`nt::deriving`
+
.. only:: html
.. contents:: Table of Contents
@@ -395,6 +399,26 @@ Arithmetic Operators
**enablement:** This operator shall be available iff. a) :cpp:type:`new_type<BaseType, TagType, DerivationClause>::base_type` is divide-assignable using the operator :literal:`/=` and b) :cpp:type:`DerivationClause` includes :cpp:var:`Arithmetic`.
+:cpp:struct:`std::hash` Support
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. cpp:namespace-pop::
+
+.. cpp:struct:: template<typename BaseType, typename TagType, auto DerivationClause> \
+ std::hash<nt::new_type<BaseType, TagType, DerivationClause>>
+
+ :tparam BaseType: |BaseTypeDoc|
+ :tparam TagType: |TagTypeDoc|
+ :tparam DerivationClause: |DerivationClauseDoc|
+
+ .. cpp:function:: constexpr std::size operator()(nt::new_type<BaseType, TagType, DerivationClause> const &) const
+
+ **enablement:** This operator shall be available iff. a) :cpp:type:`nt::new_type::base_type` is hashable and b) :cpp:var:`DerivationClause` contains :cpp:var:`nt::Hash`.
+
+ .. versionadded:: 1.0.0
+
+.. cpp:namespace-push:: nt
+
Header :literal:`<newtype/derivable.hpp>`
=========================================
@@ -433,6 +457,12 @@ Standard derivation tags
.. versionadded:: 1.0.0
+.. cpp:var:: auto constexpr Hash = derivable<struct hash_tag>{}
+
+ This tag enables the derivation of a specialization of :cpp:struct:`std::hash`
+
+ .. versionadded:: 1.0.0
+
.. cpp:var:: auto constexpr Indirection = derivable<struct indirection_tag>{}
This tag enables the derivation of the "member access through pointer" operators :cpp:func:`new_type::operator->`
diff --git a/include/newtype/derivable.hpp b/include/newtype/derivable.hpp
index 95353eb..38a2375 100644
--- a/include/newtype/derivable.hpp
+++ b/include/newtype/derivable.hpp
@@ -36,6 +36,13 @@ namespace nt
auto constexpr EqBase = derivable<struct eq_base_tag>{};
/**
+ * @brief A tag to enable derivation of a specialization of std::hash
+ *
+ * @since 1.0.0
+ */
+ auto constexpr Hash = derivable<struct hash_tag>{};
+
+ /**
* @brief A tag to enable derivation of the implicit "conversion to base type" operator
*
* @note If this tag is not present in the derivation clause of any given nt::new_type, the type instance only supports explicit
diff --git a/include/newtype/impl/type_traits_extensions.hpp b/include/newtype/impl/type_traits_extensions.hpp
index 10dbc07..d773d40 100644
--- a/include/newtype/impl/type_traits_extensions.hpp
+++ b/include/newtype/impl/type_traits_extensions.hpp
@@ -1,6 +1,8 @@
#ifndef NEWTYPE_IMPL_TYPE_TRAITS_EXTENSIONS_HPP
#define NEWTYPE_IMPL_TYPE_TRAITS_EXTENSIONS_HPP
+#include <cstddef>
+#include <functional>
#include <iosfwd>
#include <type_traits>
@@ -1011,6 +1013,42 @@ namespace nt::impl
} // namespace compound_arithmetic
+ inline namespace std_support
+ {
+
+ /**
+ * @brief A trait to test if a given type is hashable
+ *
+ * @tparam T The type to test
+ * @note This specialization forms the base case for non-hashable T
+ */
+ template<typename T, typename = void>
+ struct is_hashable : std::false_type
+ {
+ };
+
+ /**
+ * @brief A trait to test if a given type is hashable
+ *
+ * @tparam T The type to test
+ * @note This specialization forms the case for hashable T
+ */
+ template<typename T>
+ struct is_hashable<T, std::void_t<decltype(std::declval<std::hash<T> const &>()(std::declval<T const &>()))>>
+ : std::is_same<std::size_t, decltype(std::declval<std::hash<T> const &>()(std::declval<T const &>()))>
+ {
+ };
+
+ /**
+ * @brief A variable template to test if a given type is hashable
+ *
+ * @tparam T The type to test
+ */
+ template<typename T>
+ auto constexpr is_hashable_v = is_hashable<T>::value;
+
+ } // namespace std_support
+
} // namespace nt::impl
#endif \ No newline at end of file
diff --git a/include/newtype/new_type.hpp b/include/newtype/new_type.hpp
index a59a074..1477c95 100644
--- a/include/newtype/new_type.hpp
+++ b/include/newtype/new_type.hpp
@@ -6,6 +6,7 @@
#include "newtype/impl/new_type_storage.hpp"
#include "newtype/impl/type_traits_extensions.hpp"
+#include <functional>
#include <istream>
#include <ostream>
#include <type_traits>
@@ -569,4 +570,19 @@ namespace nt
} // namespace nt
+namespace std
+{
+ template<typename BaseType, typename TagType, auto DerivationClause>
+ struct hash<nt::new_type<BaseType, TagType, DerivationClause>>
+ {
+ template<typename BaseTypeT = BaseType, auto DerivationClauseV = DerivationClause>
+ auto constexpr operator()(nt::new_type<BaseType, TagType, DerivationClause> const & object,
+ std::enable_if_t<DerivationClauseV(nt::Hash) && nt::impl::is_hashable_v<BaseTypeT>> * = nullptr) const
+ -> std::size_t
+ {
+ return std::hash<BaseType>{}(object.decay());
+ }
+ };
+} // namespace std
+
#endif
diff --git a/test/include/hash_suite.hpp b/test/include/hash_suite.hpp
new file mode 100644
index 0000000..0ef51bc
--- /dev/null
+++ b/test/include/hash_suite.hpp
@@ -0,0 +1,11 @@
+#ifndef NEWTYPE_TEST_HASH_SUITE_HPP
+#define NEWTYPE_TEST_HASH_SUITE_HPP
+
+#include <cute/cute_suite.h>
+
+#include <string>
+#include <utility>
+
+auto hash_suite() -> std::pair<cute::suite, std::string>;
+
+#endif \ No newline at end of file
diff --git a/test/include/kawaii.hpp b/test/include/kawaii.hpp
index 69da012..9084b56 100644
--- a/test/include/kawaii.hpp
+++ b/test/include/kawaii.hpp
@@ -18,7 +18,7 @@ namespace nt::test
{
auto constexpr prepositions = std::array{"a", "an", "and", "as", "at", "by", "for", "in", "of", "on", "or", "the", "to"};
auto constexpr keywords = std::array{"noexcept"};
- auto constexpr type_names = std::array{"new_type", "derivation_clause"};
+ auto constexpr type_names = std::array{"new_type", "derivation_clause", "unordered_map"};
auto inline replace_template_argument_syntax(std::string const & name) -> std::string
{
diff --git a/test/src/arithmetic_suite.cpp b/test/src/arithmetic_suite.cpp
index f831b98..2e7f1fd 100644
--- a/test/src/arithmetic_suite.cpp
+++ b/test/src/arithmetic_suite.cpp
@@ -1,4 +1,5 @@
-#include "conversion_suite.hpp"
+#include "arithmetic_suite.hpp"
+
#include "kawaii.hpp"
#include "newtype/derivable.hpp"
#include "newtype/deriving.hpp"
diff --git a/test/src/driver.cpp b/test/src/driver.cpp
index c93f157..cfd6b90 100644
--- a/test/src/driver.cpp
+++ b/test/src/driver.cpp
@@ -2,6 +2,7 @@
#include "conversion_suite.hpp"
#include "derivation_clause_suite.hpp"
#include "equality_comparison_suite.hpp"
+#include "hash_suite.hpp"
#include "io_operators_suite.hpp"
#include "new_type_constructor_suite.hpp"
#include "relational_operators_suite.hpp"
@@ -59,6 +60,7 @@ int main(int argc, char ** argv)
relational_operators_suite(),
io_operators_suite(),
arithmetic_suite(),
+ hash_suite(),
};
auto selectors = get_test_selectors(suites);
diff --git a/test/src/hash_suite.cpp b/test/src/hash_suite.cpp
new file mode 100644
index 0000000..9ca362c
--- /dev/null
+++ b/test/src/hash_suite.cpp
@@ -0,0 +1,69 @@
+#include "hash_suite.hpp"
+
+#include "kawaii.hpp"
+#include "newtype/derivable.hpp"
+#include "newtype/deriving.hpp"
+#include "newtype/impl/type_traits_extensions.hpp"
+#include "newtype/new_type.hpp"
+
+#include <cute/cute.h>
+
+#include <unordered_map>
+
+inline namespace hashable_tests
+{
+
+ auto a_new__type_that_does_not_include_hash_in_its_derivation_clause_is_not_hashable() -> void
+ {
+ using type_alias = nt::new_type<int, struct tag>;
+ ASSERT(!nt::impl::is_hashable_v<type_alias>);
+ }
+
+ auto a_new__type_that_does_include_hash_in_its_derivation_clause_is_hashable() -> void
+ {
+ static_assert(nt::impl::is_hashable_v<int>, "Sanity Check");
+ using type_alias = nt::new_type<int, struct tag, deriving(nt::Hash)>;
+ ASSERT(nt::impl::is_hashable_v<type_alias>);
+ }
+
+ auto a_new__type_that_does_include_hash_in_its_derivation_clause_but_whose_base_type_is_not_hashable_is_also_not_hashable() -> void
+ {
+ struct not_hashable
+ {
+ };
+
+ static_assert(!nt::impl::is_hashable_v<not_hashable>, "Sanity Check");
+ using type_alias = nt::new_type<not_hashable, struct tag, deriving(nt::Hash)>;
+ ASSERT(!nt::impl::is_hashable_v<type_alias>);
+ }
+
+} // namespace hashable_tests
+
+inline namespace usage_tests
+{
+
+ auto a_new__type_that_is_hashable_can_be_used_in_an_unordered__map() -> void
+ {
+ static_assert(nt::impl::is_hashable_v<int>, "Sanity Check");
+ using type_alias = nt::new_type<int, struct tag, deriving(nt::Hash)>;
+
+ auto map = std::unordered_map<type_alias, int>{};
+ map[type_alias{42}] = 43;
+ ASSERT_EQUAL(43, map[type_alias{42}]);
+ }
+
+} // namespace usage_tests
+
+auto hash_suite() -> std::pair<cute::suite, std::string>
+{
+ return {{
+ // Hashable Tests
+ KAWAII(a_new__type_that_does_not_include_hash_in_its_derivation_clause_is_not_hashable),
+ KAWAII(a_new__type_that_does_include_hash_in_its_derivation_clause_is_hashable),
+ KAWAII(a_new__type_that_does_include_hash_in_its_derivation_clause_but_whose_base_type_is_not_hashable_is_also_not_hashable),
+
+ // Usage Tests
+ KAWAII(a_new__type_that_is_hashable_can_be_used_in_an_unordered__map),
+ },
+ "std::hash Support Tests"};
+} \ No newline at end of file