diff options
| author | Felix Morgner <felix.morgner@gmail.com> | 2020-01-03 13:10:02 +0100 |
|---|---|---|
| committer | Felix Morgner <felix.morgner@gmail.com> | 2020-01-03 13:10:02 +0100 |
| commit | d5970f8ed49a15d19265c71c8618e32a9534eeee (patch) | |
| tree | c03bc611027f32d9639bfb645523e9bd29b68f42 | |
| parent | 8551177740f435bd510df6af3fa3d32da39bb526 (diff) | |
| download | newtype-d5970f8ed49a15d19265c71c8618e32a9534eeee.tar.xz newtype-d5970f8ed49a15d19265c71c8618e32a9534eeee.zip | |
new_type: implement support for derivation of Hash
| -rw-r--r-- | CMakeLists.txt | 1 | ||||
| -rw-r--r-- | doc/src/index.rst | 30 | ||||
| -rw-r--r-- | include/newtype/derivable.hpp | 7 | ||||
| -rw-r--r-- | include/newtype/impl/type_traits_extensions.hpp | 38 | ||||
| -rw-r--r-- | include/newtype/new_type.hpp | 16 | ||||
| -rw-r--r-- | test/include/hash_suite.hpp | 11 | ||||
| -rw-r--r-- | test/include/kawaii.hpp | 2 | ||||
| -rw-r--r-- | test/src/arithmetic_suite.cpp | 3 | ||||
| -rw-r--r-- | test/src/driver.cpp | 2 | ||||
| -rw-r--r-- | test/src/hash_suite.cpp | 69 |
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 |
