diff --git a/CMakeLists.txt b/CMakeLists.txt index 9af74e0..1a96874 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,7 @@ if(ENABLE_PROFILING) target_link_options(rexy PRIVATE -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls) endif() -set(LIBREXY_PUBLIC_HEADERS "include/rexy/steal.hpp" "include/rexy/binary.hpp" "include/rexy/string_base.hpp" "include/rexy/string.hpp" "include/rexy/filerd.hpp" "include/rexy/string_base.tpp") +set(LIBREXY_PUBLIC_HEADERS "include/rexy/traits.hpp" "include/rexy/steal.hpp" "include/rexy/binary.hpp" "include/rexy/string_base.hpp" "include/rexy/string.hpp" "include/rexy/filerd.hpp" "include/rexy/string_base.tpp") target_compile_options(rexy PRIVATE -Wall -Wextra -pedantic -std=c++17) install(TARGETS rexy diff --git a/include/rexy/cx/algorithm.hpp b/include/rexy/cx/algorithm.hpp index 1855ef6..dc70fce 100644 --- a/include/rexy/cx/algorithm.hpp +++ b/include/rexy/cx/algorithm.hpp @@ -30,15 +30,15 @@ namespace rexy::cx{ auto value = *pivot; //move pivot value all the way to the right side to preserve it - swap(*pivot, *right); + cx::swap(*pivot, *right); for(auto it = left;it != right;++it){ if(cmp(*it, value)){ - swap(*left, *it); + cx::swap(*left, *it); ++left; } } //move pivot value back to proper position - swap(*left, *right); + cx::swap(*left, *right); return left; } template diff --git a/include/rexy/cx/hashmap.hpp b/include/rexy/cx/hashmap.hpp index 899b49f..2e65178 100644 --- a/include/rexy/cx/hashmap.hpp +++ b/include/rexy/cx/hashmap.hpp @@ -25,13 +25,18 @@ #include "hash.hpp" #include //CHAR_BIT +#include //size_t, ptrdiff_t +#include //pair +#include //decay #include namespace rexy::cx{ template - struct element - { + struct element{ + using key_type = Key; + using value_type = Value; + Key key; Value value; }; @@ -40,103 +45,126 @@ namespace rexy::cx{ class hashmap { public: - static constexpr size_t single_bucket_bit = size_t{1} << ((sizeof(size_t)*CHAR_BIT) - 1); - static constexpr size_t max_size = N; + using key_type = Key; + using mapped_type = Value; + using value_type = element; + using size_type = size_t; + using difference_type = ptrdiff_t; + using hasher = Hash; + using reference = mapped_type&; + using const_reference = const mapped_type&; + using pointer = mapped_type*; + using const_pointer = const mapped_type*; + + static constexpr size_type single_bucket_bit = size_type{1} << ((sizeof(size_type)*CHAR_BIT) - 1); + static constexpr size_type max_size = N; static_assert((max_size & single_bucket_bit) == 0); - using key_type = Key; - using value_type = Value; - using hash_type = Hash; private: - array m_values; //perfect hash table - array m_g; //'salt' values for indexing into the perfect hash table + array m_values; //perfect hash table + array m_g; //'salt' values for indexing into the perfect hash table public: - constexpr hashmap(const element(&elements)[N]){ - array,N>,N> buckets; - array slots_used; - size_t current_bucket = 0; - - //place all keys into buckets - for(auto& element : elements){ - buckets[Hash{}(element.key) % max_size].push_back(element); - } - - //sort the buckets based on size, largest first - quicksort(buckets.begin(), buckets.end(), [](auto&& left, auto&& right) -> bool{ - return left.size() > right.size(); - }); - - //for each bucket, try different values of 'd' to try to find a hash that doesn't collide - for(current_bucket = 0;current_bucket < buckets.size();++current_bucket){ - auto& bucket = buckets[current_bucket]; - //only handle buckets containing collisions - if(bucket.size() <= 1) - break; - - array pass_slots_used; - vector pass_slots; - size_t d = 1; - - for(size_t i = 0;i < bucket.size();){ - size_t slot = Hash{}(bucket[i].key, d) % max_size; - if(pass_slots_used[slot] || slots_used[slot]){ - //slot already in use, try another value for 'd' - ++d; - i = 0; - pass_slots_used.fill(false); - pass_slots.clear(); - }else{ - //slot is good to go - pass_slots_used[slot] = true; - pass_slots.push_back(slot); - ++i; - } - } - //store the successful value of 'd' at index of the first hash for this bucket - m_g[Hash{}(bucket[0].key) % max_size] = d; - - //take the value from the temporary bucket into the permanent slot - for(size_t i = 0;i < bucket.size();++i){ - m_values[pass_slots[i]] = std::move(bucket[i].value); - slots_used[pass_slots[i]] = true; - } - } - - //Handle remaining single value buckets - size_t next_free_slot = 0; - - for(;current_bucket < buckets.size();++current_bucket){ - auto& bucket = buckets[current_bucket]; - if(bucket.size() == 0) - break; - for(;slots_used[next_free_slot];++next_free_slot); - - m_g[Hash{}(bucket[0].key) % max_size] = (next_free_slot | single_bucket_bit); - m_values[next_free_slot] = std::move(bucket[0].value); - slots_used[next_free_slot] = true; - } - } + constexpr hashmap(const value_type(&elements)[N]); //no key checks. give a correct key or get a random answer :) - constexpr value_type& operator[](const Key& key){ - size_t d = m_g[Hash{}(key) % max_size]; - if(d & single_bucket_bit) - return m_values[d & ~single_bucket_bit]; - return m_values[Hash{}(key, d) % max_size]; - } - constexpr const value_type& operator[](const Key& key)const{ - size_t d = m_g[Hash{}(key) % max_size]; - if(d & single_bucket_bit) - return m_values[d & ~single_bucket_bit]; - return m_values[Hash{}(key, d) % max_size]; - } + template>> + constexpr reference operator[](U&& u); + template>> + constexpr const_reference operator[](U&& u)const; }; + template + constexpr hashmap::hashmap(const value_type(&elements)[N]){ + array,N> buckets; + array slots_used; + size_type current_bucket = 0; + + //place all keys into buckets + for(auto& element : elements){ + buckets[Hash{}(element.key) % max_size].push_back(element); + } + + //sort the buckets based on size, largest first + quicksort(buckets.begin(), buckets.end(), [](auto&& left, auto&& right) -> bool{ + return left.size() > right.size(); + }); + + //for each bucket, try different values of 'd' to try to find a hash that doesn't collide + for(current_bucket = 0;current_bucket < buckets.size();++current_bucket){ + auto& bucket = buckets[current_bucket]; + //only handle buckets containing collisions + if(bucket.size() <= 1) + break; + + array pass_slots_used; + vector pass_slots; + size_type d = 1; + + for(size_type i = 0;i < bucket.size();){ + size_type slot = Hash{}(bucket[i].key, d) % max_size; + if(pass_slots_used[slot] || slots_used[slot]){ + //slot already in use, try another value for 'd' + ++d; + i = 0; + pass_slots_used.fill(false); + pass_slots.clear(); + }else{ + //slot is good to go + pass_slots_used[slot] = true; + pass_slots.push_back(slot); + ++i; + } + } + //store the successful value of 'd' at index of the first hash for this bucket + m_g[Hash{}(bucket[0].key) % max_size] = d; + + //take the value from the temporary bucket into the permanent slot + for(size_type i = 0;i < bucket.size();++i){ + m_values[pass_slots[i]] = std::move(bucket[i].value); + slots_used[pass_slots[i]] = true; + } + } + + //Handle remaining single value buckets + size_type next_free_slot = 0; + + for(;current_bucket < buckets.size();++current_bucket){ + auto& bucket = buckets[current_bucket]; + if(bucket.size() == 0) + break; + for(;slots_used[next_free_slot];++next_free_slot); + + m_g[Hash{}(bucket[0].key) % max_size] = (next_free_slot | single_bucket_bit); + m_values[next_free_slot] = std::move(bucket[0].value); + slots_used[next_free_slot] = true; + } + } + + //no key checks. give a correct key or get a random answer :) + template + template + constexpr auto hashmap::operator[](U&& key) -> reference{ + auto d = m_g[UHash{}(std::forward(key)) % max_size]; + if(d & single_bucket_bit) + return m_values[d & ~single_bucket_bit]; + return m_values[UHash{}(std::forward(key), d) % max_size]; + } + template + template + constexpr auto hashmap::operator[](U&& key)const -> const_reference{ + auto d = m_g[UHash{}(std::forward(key)) % max_size]; + if(d & single_bucket_bit) + return m_values[d & ~single_bucket_bit]; + return m_values[UHash{}(std::forward(key), d) % max_size]; + } + + + template> - constexpr auto make_hashmap(const element(&list)[N]){ + constexpr auto make_hashmap(const typename hashmap::value_type(&list)[N]){ return hashmap(list); } } diff --git a/include/rexy/string_base.hpp b/include/rexy/string_base.hpp index 58add75..5a2460a 100644 --- a/include/rexy/string_base.hpp +++ b/include/rexy/string_base.hpp @@ -25,6 +25,7 @@ #include "steal.hpp" #include "cx/utility.hpp" +#include "traits.hpp" namespace rexy{ @@ -166,36 +167,36 @@ namespace rexy{ namespace detail{ - std::true_type is_string_helper(string_expr*); - std::false_type is_string_helper(...); template struct is_string{ - static constexpr bool value = std::is_same::type*>()))>::value; + static constexpr bool value = rexy::is_type::value; }; - std::true_type is_string_base(string_base*); - std::false_type is_string_base(...); template struct is_concrete_string{ - static constexpr bool value = std::is_same::type*>()))>::value; + static constexpr bool value = rexy::is_type::value; }; template using enable_if_string = std::enable_if_t<(is_string::value && ...),int>; template using enable_if_concrete_string = std::enable_if_t<(is_concrete_string::value && ...),int>; + template + using enable_if_expr_string = std::enable_if_t<(rexy::is_template_type::value && ...),int>; template - struct appender + struct string_appender { private: Targ& m_targ; public: - appender(Targ& t); + constexpr string_appender(Targ& t); template - void operator()(const string_cat_expr& str); - void operator()(const string_base& str); + constexpr void operator()(const string_cat_expr& str); + template::value,int> = 0> + constexpr void operator()(Str&& str); }; } //namespace detail + template = 0> constexpr bool operator==(Str1&& left, Str2&& right){ return left.valid() && right.valid() && left.length() == right.length() && !cx::strcmp(left.get(), right.get()); diff --git a/include/rexy/string_base.tpp b/include/rexy/string_base.tpp index c1712f2..48ab29b 100644 --- a/include/rexy/string_base.tpp +++ b/include/rexy/string_base.tpp @@ -219,7 +219,7 @@ namespace rexy{ string_cat_expr::operator string_intermediary(void){ size_t len = length(); string_intermediary ret(len); - detail::appender> append(ret); + detail::string_appender> append(ret); append(*this); return ret; } @@ -259,15 +259,16 @@ namespace rexy{ namespace detail{ template - appender::appender(Targ& t): m_targ(t){} + constexpr string_appender::string_appender(Targ& t): m_targ(t){} template template - void appender::operator()(const string_cat_expr& str){ + constexpr void string_appender::operator()(const string_cat_expr& str){ (*this)(str.left()); (*this)(str.right()); } template - void appender::operator()(const string_base& str){ + template::value,int>> + constexpr void string_appender::operator()(Str&& str){ m_targ.append(str.get(), str.length()); } } diff --git a/include/rexy/traits.hpp b/include/rexy/traits.hpp new file mode 100644 index 0000000..5d8bf87 --- /dev/null +++ b/include/rexy/traits.hpp @@ -0,0 +1,29 @@ +#ifndef REXY_TRAITS_HPP +#define REXY_TRAITS_HPP + +#include //is_same, decay, integral_constant, declval + +namespace rexy{ + + template + struct is_type{ + static std::true_type check(U*); + static std::false_type check(...); + + static constexpr bool value = std::is_same*>()))>::value; + }; + template class U> + struct is_template_type_helper{ + static constexpr bool value = false; + }; + template class U, class... Args> + struct is_template_type_helper,U>{ + static constexpr bool value = true; + }; + + template class U> + struct is_template_type : public is_template_type_helper,U>{}; + +} + +#endif