diff --git a/string-switch-map/StringMap.hpp b/string-switch-map/StringMap.hpp index a26dc68..4f467b3 100644 --- a/string-switch-map/StringMap.hpp +++ b/string-switch-map/StringMap.hpp @@ -25,20 +25,29 @@ #define STRING_MAP_HAS_BIT 0 #endif +#if defined(__cpp_constexpr) && __cpp_constexpr >= 201907L && \ + defined(__cpp_lib_constexpr_dynamic_alloc) && __cpp_lib_constexpr_dynamic_alloc >= 201907L && \ + !defined(_MSC_VER) && !defined(_GLIBCXX_DEBUG) +#define CUSTOM_CONSTEXPR_VEC_FOR_OLD_COMPILERS 0 +#include +#else +#define CUSTOM_CONSTEXPR_VEC_FOR_OLD_COMPILERS 1 +#endif + #if defined(__cpp_consteval) && __cpp_consteval >= 201811L #define STRING_MAP_CONSTEVAL consteval #else #define STRING_MAP_CONSTEVAL constexpr #endif -namespace string_map_detail { +namespace strmapdetail { inline constexpr std::size_t kMaxStringViewSize = 200; template + requires(N > 0) struct [[nodiscard]] CompileTimeStringLiteral { - static_assert(N > 0); - STRING_MAP_CONSTEVAL CompileTimeStringLiteral(std::string_view str) noexcept + /* implicit */ STRING_MAP_CONSTEVAL CompileTimeStringLiteral(std::string_view str) noexcept : length(str.size()) { const bool fits_in_buffer = str.size() < std::size(value); // HINT: Change kMaxStringViewSize if you are using very long strings @@ -46,7 +55,7 @@ struct [[nodiscard]] CompileTimeStringLiteral { [[maybe_unused]] const auto string_view_size_check = 0 / int{fits_in_buffer}; std::char_traits::copy(value.data(), str.data(), str.size()); } - STRING_MAP_CONSTEVAL CompileTimeStringLiteral(const char (&str)[N]) noexcept + /* implicit */ STRING_MAP_CONSTEVAL CompileTimeStringLiteral(const char (&str)[N]) noexcept : length(std::char_traits::length(str)) { std::char_traits::copy(value.data(), str, size()); } @@ -60,10 +69,6 @@ struct [[nodiscard]] CompileTimeStringLiteral { ATTRIBUTE_LIFETIME_BOUND { return value[index]; } - [[nodiscard]] STRING_MAP_CONSTEVAL std::string_view as_string_view() const noexcept - ATTRIBUTE_LIFETIME_BOUND { - return std::string_view(value.data(), size()); - } // clang-format off template @@ -74,13 +79,22 @@ struct [[nodiscard]] CompileTimeStringLiteral { // clang-format on static_assert(std::is_same_v || std::is_same_v); if constexpr (std::is_same_v) { - return str1.as_string_view() == str2; + // replacing .length with .size() may lead gcc 13.2.0 to + // say str1 is not a constant expression + return std::string_view(str1.value.data(), str1.length) == str2; } else if (std::is_constant_evaluated()) { +#if STRING_MAP_HAS_BIT const auto uvalue = std::bit_cast>(str1.value); - return std::basic_string_view(uvalue.data(), str1.length) == str2; +#else + std::array uvalue{}; + for (std::size_t i = 0; i < str1.size(); i++) { + uvalue[i] = static_cast(str1[i]); + } +#endif + return std::basic_string_view(uvalue.data(), str1.size()) == str2; } else { const std::string_view cstr2(reinterpret_cast(str2.data()), str2.size()); - return str1.as_string_view() == cstr2; + return std::string_view(str1.value.data(), str1.size()) == cstr2; } } // clang-format off template @@ -122,8 +136,8 @@ struct MinMaxCharsType { std::uint32_t max_char; }; -template +template STRING_MAP_CONSTEVAL MinMaxCharsType FindMinMaxChars() noexcept { static_assert(FirstString.size() > 0, "Empty string was passed in StringMatch / StringMap"); @@ -148,26 +162,33 @@ STRING_MAP_CONSTEVAL MinMaxCharsType FindMinMaxChars() noexcept { } template -struct CountingVector final { - struct CountingNode final { - std::array edges{}; - }; +struct CountingNode final { + std::array edges{}; +}; + +#if CUSTOM_CONSTEXPR_VEC_FOR_OLD_COMPILERS - using value_type = CountingNode; +template +struct CountingVector final { + using value_type = CountingNode; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using size_type = std::size_t; struct CountingVectorAllocatorImpl final { - [[nodiscard]] constexpr value_type* allocate(std::size_t size) const { + [[nodiscard]] constexpr pointer allocate(size_type size) const { // Can't call ::operator new(...) in the constexpr context return new value_type[size](); } - constexpr void deallocate(value_type* ptr, - [[maybe_unused]] std::size_t size) const noexcept { + constexpr void deallocate(pointer ptr, [[maybe_unused]] size_type size) const noexcept { delete[] ptr; } }; using allocator_type = CountingVectorAllocatorImpl; - explicit constexpr CountingVector(std::size_t size) + explicit constexpr CountingVector(size_type size) : size_(size), capacity_(size_ > 16 ? size_ : 16) { data_ = allocator_type{}.allocate(capacity_); } @@ -206,26 +227,20 @@ struct CountingVector final { allocator_type{}.deallocate(data_, capacity_); data_ = nullptr; } - [[nodiscard]] constexpr std::size_t size() const noexcept { + [[nodiscard]] constexpr size_type size() const noexcept { return size_; } - [[nodiscard]] constexpr std::size_t capacity() const noexcept { - return capacity_; - } - [[nodiscard]] constexpr bool empty() const noexcept { - return size_ == 0; - } - [[nodiscard]] constexpr value_type& operator[](std::size_t index) noexcept { + [[nodiscard]] constexpr value_type& operator[](size_type index) noexcept { return data_[index]; } - [[nodiscard]] constexpr const value_type& operator[](std::size_t index) const noexcept { + [[nodiscard]] constexpr const value_type& operator[](size_type index) const noexcept { return data_[index]; } - constexpr void emplace_back_empty_node() { + constexpr reference emplace_back() ATTRIBUTE_LIFETIME_BOUND { if (size_ == capacity_) { growth_storage(); } - ++size_; + return data_[size_++]; } constexpr void growth_storage() { const auto new_capacity = capacity_ > 0 ? capacity_ * 2 : 16; @@ -243,14 +258,18 @@ struct CountingVector final { capacity_ = new_capacity; } - value_type* data_{}; - std::size_t size_{}; - std::size_t capacity_{}; + pointer data_{}; + size_type size_{}; + size_type capacity_{}; }; -template +#else +template +using CountingVector = std::vector>; +#endif + +template STRING_MAP_CONSTEVAL std::pair CountNodesSizeAndMaxHeightImpl( CountingVector& nodes, std::size_t max_seen_height) { std::size_t current_node_index = 0; @@ -260,7 +279,7 @@ STRING_MAP_CONSTEVAL std::pair CountNodesSizeAndMaxHei std::size_t next_node_index = nodes[current_node_index].edges[index]; if (next_node_index == 0) { std::size_t new_node_index = nodes.size(); - nodes.emplace_back_empty_node(); + nodes.emplace_back(); nodes[current_node_index].edges[index] = static_cast(new_node_index); next_node_index = new_node_index; } @@ -276,7 +295,7 @@ STRING_MAP_CONSTEVAL std::pair CountNodesSizeAndMaxHei } } -template +template STRING_MAP_CONSTEVAL std::pair CountNodesSizeAndMaxHeight() { constexpr auto kAlphabetSize = TrieParams.trie_alphabet_size; CountingVector nodes(std::size_t{1}); @@ -284,7 +303,7 @@ STRING_MAP_CONSTEVAL std::pair CountNodesSizeAndMaxHei return CountNodesSizeAndMaxHeightImpl(nodes, max_seen_height); } -template +template STRING_MAP_CONSTEVAL TrieParamsType TrieParams() { constexpr MinMaxCharsType kMinMaxChars = FindMinMaxChars(); constexpr TrieParamsType kTrieParamsProto = { @@ -301,12 +320,12 @@ STRING_MAP_CONSTEVAL TrieParamsType TrieParams() { }; } -template +template inline constexpr TrieParamsType kTrieParams = TrieParams(); } // namespace trie_tools -namespace string_map_impl { +namespace impl { template ); + static_assert(std::is_same_v>); + static_assert(std::is_default_constructible_v && + std::is_copy_assignable_v); static constexpr MappedType kDefaultValue = DefaultMapValue; static constexpr char kMinChar = static_cast(TrieParams.min_char); @@ -482,8 +503,8 @@ class [[nodiscard]] StringMapImplManyStrings final { #endif ; - template + template STRING_MAP_CONSTEVAL void AddPattern(std::size_t first_free_node_index) noexcept { std::size_t current_node_index = 0; constexpr std::size_t len = String.size(); @@ -501,7 +522,7 @@ class [[nodiscard]] StringMapImplManyStrings final { const bool already_added_string = nodes_[current_node_index].node_value != kDefaultValue; // HINT: Remove duplicate strings from the StringMatch / StringMap - [[maybe_unused]] const auto duplicate_strings_check = 0 / !already_added_string; + [[maybe_unused]] const auto duplicate_strings_check = 0 / int{!already_added_string}; static_assert(CurrentPackIndex < MappedValues.size(), "impl error"); nodes_[current_node_index].node_value = MappedValues[CurrentPackIndex]; @@ -537,8 +558,9 @@ class [[nodiscard]] StringMapImplManyStrings final { const auto returned_value = nodes_[current_node_index].node_value; if constexpr (kMappedTypesInfo.ordered) { - if (returned_value != kDefaultValue && (returned_value < kMappedTypesInfo.min_value || - returned_value > kMappedTypesInfo.max_value)) { + if (returned_value != kDefaultValue && + (std::less{}(returned_value, kMappedTypesInfo.min_value) || + std::greater{}(returned_value, kMappedTypesInfo.max_value))) { CONFIG_UNREACHABLE(); } } @@ -550,12 +572,18 @@ class [[nodiscard]] StringMapImplManyStrings final { static constexpr bool kMaybeOrdered = std::is_arithmetic_v || std::is_pointer_v || std::is_member_pointer_v || - (std::is_enum_v && requires(MappedType x, MappedType y) { - { std::less{}(x, y) } -> std::same_as; - { std::less_equal{}(x, y) } -> std::same_as; - { std::greater{}(x, y) } -> std::same_as; - { std::greater_equal{}(x, y) } -> std::same_as; - }); + (!std::is_polymorphic_v && + std::is_nothrow_default_constructible_v && + std::is_nothrow_copy_constructible_v && + std::is_nothrow_copy_assignable_v && + std::is_nothrow_move_constructible_v && + std::is_nothrow_move_assignable_v && + std::is_nothrow_destructible_v && requires(MappedType x, MappedType y) { + { std::less{}(x, y) } -> std::same_as; + { std::less_equal{}(x, y) } -> std::same_as; + { std::greater{}(x, y) } -> std::same_as; + { std::greater_equal{}(x, y) } -> std::same_as; + }); using DefaultConstructibleSentinelType = std::conditional_t, MappedType, int>; @@ -702,49 +730,29 @@ class [[nodiscard]] StringMapImplFewStrings final { } }; -} // namespace string_map_impl +} // namespace impl -} // namespace string_map_detail +} // namespace strmapdetail -template +template struct StringKeys; template -struct MapValues { +struct StringMapValues { std::array values; }; #if defined(__cpp_deduction_guides) && __cpp_deduction_guides >= 201606 template -MapValues(T, Ts...) - -> MapValues && ...), T>, 1 + sizeof...(Ts)>; +StringMapValues(T, Ts...) + -> StringMapValues && ...), T>, 1 + sizeof...(Ts)>; #endif -namespace string_map_detail { - -template -struct StringHelper; - -template - requires(sizeof...(Strings) == std::size(MappedValues.values) && - std::size(MappedValues.values) > 0) && - std::is_same_v -struct StringHelper, MappedValues, MappedDefaultValue> { - using type = typename std::conditional_t< - (sizeof...(Strings) <= 4 && - string_map_detail::trie_tools::kTrieParams.max_tree_height <= 15), - typename string_map_detail::string_map_impl::StringMapImplFewStrings< - string_map_detail::trie_tools::kTrieParams, MappedValues.values, - MappedDefaultValue, Strings...>, - typename string_map_detail::string_map_impl::StringMapImplManyStrings< - string_map_detail::trie_tools::kTrieParams, MappedValues.values, - MappedDefaultValue, Strings...>>; -}; +namespace strmapdetail { template -STRING_MAP_CONSTEVAL MapValues make_index_array_for_map() noexcept { - MapValues index_array{}; +STRING_MAP_CONSTEVAL StringMapValues make_index_array() noexcept { + StringMapValues index_array{}; #if defined(__cpp_lib_constexpr_numeric) && __cpp_lib_constexpr_numeric >= 201911L && \ !defined(_GLIBCXX_DEBUG) && !defined(_LIBCPP_ENABLE_ASSERTIONS) std::iota(index_array.values.begin(), index_array.values.end(), 0); @@ -756,16 +764,45 @@ STRING_MAP_CONSTEVAL MapValues make_index_array_for_map() noexce return index_array; } -} // namespace string_map_detail +template +struct StringHelper; + +template +struct StringHelper, MappedValues, MappedDefaultValue> { + static_assert(sizeof...(Strings) == std::size(MappedValues.values), + "StringMap should has equal number of keys and values"); + + static_assert(std::size(MappedValues.values) > 0, + "StringMap or StringMatch should has at least one string key"); + + static_assert(std::is_same_v, + "Default value of the StringMap should be of the same type as StringMap values"); + + using type = typename std::conditional_t< + (sizeof...(Strings) <= 4 && + strmapdetail::trie_tools::kTrieParams.max_tree_height <= 15), + typename strmapdetail::impl::StringMapImplFewStrings< + strmapdetail::trie_tools::kTrieParams, MappedValues.values, + MappedDefaultValue, Strings...>, + typename strmapdetail::impl::StringMapImplManyStrings< + strmapdetail::trie_tools::kTrieParams, MappedValues.values, + MappedDefaultValue, Strings...>>; +}; + +} // namespace strmapdetail +#undef CUSTOM_CONSTEXPR_VEC_FOR_OLD_COMPILERS #undef STRING_MAP_CONSTEVAL #undef STRING_MAP_HAS_BIT #undef STRING_MAP_HAS_SPAN -template -using StringMap = typename string_map_detail::StringHelper::type; +template +using StringMap = typename strmapdetail::StringHelper::type; -template -using StringMatch = StringMap, - string_map_detail::make_index_array_for_map(), - sizeof...(Strings)>; +template +using StringMatch = + StringMap, strmapdetail::make_index_array(), + sizeof...(Strings)>; diff --git a/string-switch-map/tests/test_string_switch_map.cpp b/string-switch-map/tests/test_string_switch_map.cpp index a793b78..b3b2946 100644 --- a/string-switch-map/tests/test_string_switch_map.cpp +++ b/string-switch-map/tests/test_string_switch_map.cpp @@ -9,6 +9,8 @@ #include "../config_macros.hpp" #include "../StringMap.hpp" +namespace { + // clang-format off inline constexpr std::string_view kStrings[] = { "abcdefghijklmnopqrstuvwxyz", @@ -83,7 +85,12 @@ constexpr uint64_t operator-(const timespec& t2, const timespec& t1) noexcept { return nanoseconds_passed; } -static void run_bench() { +#if (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L) || \ + ((defined(__APPLE__) || defined(__linux__)) && (defined(__GNUG__) || defined(__clang__))) + +#define CAN_RUN_BENCHMARK + +void run_bench() { constexpr auto kMeasureLimit = 10000u; static constexpr auto sw = StringMatch< @@ -122,6 +129,10 @@ static void run_bench() { printf("%" PRIu64 " nanoseconds on average\n", (t2 - t1) / kMeasureLimit); } +#endif + +} // namespace + int main() { { static constexpr auto sw = StringMatch<"abc", "def", "ghij", "foo", "bar", "baz", "qux", @@ -196,7 +207,7 @@ int main() { using enum SomeEnum; static constexpr auto map = StringMap, - MapValues{kText1, kText2, kText3, kText4, kText1, kText3}, kNone>(); + StringMapValues{kText1, kText2, kText3, kText4, kText1, kText3}, kNone>(); static_assert(map("text1") == kText1); static_assert(map("text2") == kText2); @@ -230,8 +241,8 @@ int main() { static constexpr auto map = StringMap, - MapValues{MyTrivialType(1, 2, 3), MyTrivialType(4, 5, 6), - MyTrivialType(7, 8, 9)}, + StringMapValues{MyTrivialType(1, 2, 3), MyTrivialType(4, 5, 6), + MyTrivialType(7, 8, 9)}, MyTrivialType(0, 0, 0)>(); static_assert(map(kMyConstants[0]) == MyTrivialType(1, 2, 3)); @@ -247,6 +258,8 @@ int main() { assert(map.kDefaultValue == MyTrivialType(0, 0, 0)); } +#ifdef CAN_RUN_BENCHMARK run_bench(); +#endif return 0; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7a117d0..d6bcc5f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -14,6 +14,8 @@ set(TEST_C_COMPILE_OPTIONS) set(TEST_CXX_COMPILE_OPTIONS) set(TEST_COMPILE_DEFINITIONS) +set(USE_CPPCHECK_DURING_BUILD False) + # Other versions can be added below depending on the compiler version set(COMPILER_SUPPORTED_C_VERSIONS "99") set(COMPILER_SUPPORTED_CXX_VERSIONS "11;14;17") @@ -39,7 +41,7 @@ if(USING_MINGW_GCC) endif() -if(CMAKE_SYSTEM_NAME MATCHES "Linux") +if(USE_CPPCHECK_DURING_BUILD AND CMAKE_SYSTEM_NAME MATCHES "Linux") find_program(CMAKE_CXX_CPPCHECK NAMES cppcheck) if(CMAKE_CXX_CPPCHECK) set(CPPCHECK_EXITCODE_ON_ERROR 0) @@ -716,7 +718,7 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clan endif() list(APPEND TestFilenames "test_string_switch_map.cpp") -list(APPEND TestDirectories "../string-switch-map/tests") +list(APPEND TestDirectories "string-switch-map tests") list(APPEND TestLangVersions "20 23 26") list(APPEND TestDependencies "") list(APPEND TestOptionalDependencies "")