From d5970f8ed49a15d19265c71c8618e32a9534eeee Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 3 Jan 2020 13:10:02 +0100 Subject: new_type: implement support for derivation of Hash --- CMakeLists.txt | 1 + doc/src/index.rst | 30 +++++++++++ include/newtype/derivable.hpp | 7 +++ include/newtype/impl/type_traits_extensions.hpp | 38 ++++++++++++++ include/newtype/new_type.hpp | 16 ++++++ test/include/hash_suite.hpp | 11 ++++ test/include/kawaii.hpp | 2 +- test/src/arithmetic_suite.cpp | 3 +- test/src/driver.cpp | 2 + test/src/hash_suite.cpp | 69 +++++++++++++++++++++++++ 10 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 test/include/hash_suite.hpp create mode 100644 test/src/hash_suite.cpp 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::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 \ + std::hash> + + :tparam BaseType: |BaseTypeDoc| + :tparam TagType: |TagTypeDoc| + :tparam DerivationClause: |DerivationClauseDoc| + + .. cpp:function:: constexpr std::size operator()(nt::new_type 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:`` ========================================= @@ -433,6 +457,12 @@ Standard derivation tags .. versionadded:: 1.0.0 +.. cpp:var:: auto constexpr Hash = derivable{} + + This tag enables the derivation of a specialization of :cpp:struct:`std::hash` + + .. versionadded:: 1.0.0 + .. cpp:var:: auto constexpr Indirection = derivable{} 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 @@ -35,6 +35,13 @@ namespace nt */ auto constexpr EqBase = derivable{}; + /** + * @brief A tag to enable derivation of a specialization of std::hash + * + * @since 1.0.0 + */ + auto constexpr Hash = derivable{}; + /** * @brief A tag to enable derivation of the implicit "conversion to base type" operator * 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 +#include #include #include @@ -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 + 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 + struct is_hashable const &>()(std::declval()))>> + : std::is_same const &>()(std::declval()))> + { + }; + + /** + * @brief A variable template to test if a given type is hashable + * + * @tparam T The type to test + */ + template + auto constexpr is_hashable_v = is_hashable::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 #include #include #include @@ -569,4 +570,19 @@ namespace nt } // namespace nt +namespace std +{ + template + struct hash> + { + template + auto constexpr operator()(nt::new_type const & object, + std::enable_if_t> * = nullptr) const + -> std::size_t + { + return std::hash{}(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 + +#include +#include + +auto hash_suite() -> std::pair; + +#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 + +#include + +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; + ASSERT(!nt::impl::is_hashable_v); + } + + auto a_new__type_that_does_include_hash_in_its_derivation_clause_is_hashable() -> void + { + static_assert(nt::impl::is_hashable_v, "Sanity Check"); + using type_alias = nt::new_type; + ASSERT(nt::impl::is_hashable_v); + } + + 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, "Sanity Check"); + using type_alias = nt::new_type; + ASSERT(!nt::impl::is_hashable_v); + } + +} // 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, "Sanity Check"); + using type_alias = nt::new_type; + + auto map = std::unordered_map{}; + map[type_alias{42}] = 43; + ASSERT_EQUAL(43, map[type_alias{42}]); + } + +} // namespace usage_tests + +auto hash_suite() -> std::pair +{ + 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 -- cgit v1.2.3