diff --git a/string-switch-map/StringMap.hpp b/string-switch-map/StringMap.hpp index 4f467b3..8b46903 100644 --- a/string-switch-map/StringMap.hpp +++ b/string-switch-map/StringMap.hpp @@ -27,7 +27,8 @@ #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) + (defined(_MSC_VER) || \ + (!defined(__GNUG__) || (CONFIG_GNUC_AT_LEAST(12, 1) && !defined(_GLIBCXX_DEBUG)))) #define CUSTOM_CONSTEXPR_VEC_FOR_OLD_COMPILERS 0 #include #else @@ -48,13 +49,15 @@ template requires(N > 0) struct [[nodiscard]] CompileTimeStringLiteral { /* implicit */ STRING_MAP_CONSTEVAL CompileTimeStringLiteral(std::string_view str) noexcept - : length(str.size()) { + : length{str.size()} { const bool fits_in_buffer = str.size() < std::size(value); // HINT: Change kMaxStringViewSize if you are using very long strings // in the StringMatch / StringMap. [[maybe_unused]] const auto string_view_size_check = 0 / int{fits_in_buffer}; std::char_traits::copy(value.data(), str.data(), str.size()); } + ATTRIBUTE_NONNULL_ALL_ARGS + ATTRIBUTE_ACCESS(read_only, 1) /* implicit */ STRING_MAP_CONSTEVAL CompileTimeStringLiteral(const char (&str)[N]) noexcept : length(std::char_traits::length(str)) { std::char_traits::copy(value.data(), str, size()); @@ -71,7 +74,7 @@ struct [[nodiscard]] CompileTimeStringLiteral { } // clang-format off - template + template [[nodiscard]] ATTRIBUTE_PURE ATTRIBUTE_ALWAYS_INLINE @@ -97,7 +100,7 @@ struct [[nodiscard]] CompileTimeStringLiteral { return std::string_view(str1.value.data(), str1.size()) == cstr2; } } // clang-format off - template + template [[nodiscard]] ATTRIBUTE_PURE ATTRIBUTE_ALWAYS_INLINE @@ -131,14 +134,14 @@ struct TrieParamsType final { } }; -struct MinMaxCharsType { - std::uint32_t min_char; - std::uint32_t max_char; +struct [[nodiscard]] MinMaxCharsType { + std::uint32_t min_char{}; + std::uint32_t max_char{}; }; template -STRING_MAP_CONSTEVAL MinMaxCharsType FindMinMaxChars() noexcept { +[[nodiscard]] STRING_MAP_CONSTEVAL MinMaxCharsType FindMinMaxChars() noexcept { static_assert(FirstString.size() > 0, "Empty string was passed in StringMatch / StringMap"); std::uint32_t min_char = FirstString[0]; @@ -162,14 +165,14 @@ STRING_MAP_CONSTEVAL MinMaxCharsType FindMinMaxChars() noexcept { } template -struct CountingNode final { +struct [[nodiscard]] CountingNode final { std::array edges{}; }; #if CUSTOM_CONSTEXPR_VEC_FOR_OLD_COMPILERS template -struct CountingVector final { +struct [[nodiscard]] CountingVector final { using value_type = CountingNode; using reference = value_type&; using const_reference = const value_type&; @@ -270,8 +273,9 @@ using CountingVector = std::vector>; template -STRING_MAP_CONSTEVAL std::pair CountNodesSizeAndMaxHeightImpl( - CountingVector& nodes, std::size_t max_seen_height) { +[[nodiscard]] STRING_MAP_CONSTEVAL std::pair +CountNodesSizeAndMaxHeightImpl(CountingVector& nodes, + std::size_t max_seen_height) { std::size_t current_node_index = 0; constexpr std::size_t len = FirstString.size(); for (std::size_t i = 0; i < len; i++) { @@ -296,7 +300,8 @@ STRING_MAP_CONSTEVAL std::pair CountNodesSizeAndMaxHei } template -STRING_MAP_CONSTEVAL std::pair CountNodesSizeAndMaxHeight() { +[[nodiscard]] STRING_MAP_CONSTEVAL std::pair +CountNodesSizeAndMaxHeight() { constexpr auto kAlphabetSize = TrieParams.trie_alphabet_size; CountingVector nodes(std::size_t{1}); std::size_t max_seen_height = 0; @@ -304,7 +309,7 @@ STRING_MAP_CONSTEVAL std::pair CountNodesSizeAndMaxHei } template -STRING_MAP_CONSTEVAL TrieParamsType TrieParams() { +[[nodiscard]] STRING_MAP_CONSTEVAL TrieParamsType TrieParams() { constexpr MinMaxCharsType kMinMaxChars = FindMinMaxChars(); constexpr TrieParamsType kTrieParamsProto = { .min_char = kMinMaxChars.min_char, @@ -327,6 +332,18 @@ inline constexpr TrieParamsType kTrieParams = TrieParams(); namespace impl { +#if defined(__GNUC__) && defined(__GNUC_MINOR__) && __GNUC__ == 13 && __GNUC_MINOR__ == 1 +#define GNU_NODES_FIELD_INIT_BUG 1 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Weffc++" +#else +#define GNU_NODES_FIELD_INIT_BUG 0 +#endif + +template +concept MappableType = std::same_as> && + std::copy_constructible && std::is_copy_assignable_v; + template @@ -346,7 +363,7 @@ class [[nodiscard]] StringMapImplManyStrings final { using pointer = value_type*; using reference = value_type&; - template + template ATTRIBUTE_NONNULL_ALL_ARGS ATTRIBUTE_ALWAYS_INLINE explicit constexpr InternalIterator( const CharType* ATTRIBUTE_LIFETIME_BOUND str) noexcept { if constexpr (InCompileTime || std::is_same_v) { @@ -378,7 +395,7 @@ class [[nodiscard]] StringMapImplManyStrings final { const InternalIterator& other) const noexcept { return pointer_ == other.pointer_; } - struct CStringSentinel {}; + struct CStringSentinel final {}; [[nodiscard]] ATTRIBUTE_PURE ATTRIBUTE_ALWAYS_INLINE constexpr bool operator==( const CStringSentinel&) const noexcept { @@ -390,38 +407,39 @@ class [[nodiscard]] StringMapImplManyStrings final { public: using MappedType = typename decltype(MappedValues)::value_type; - static_assert(std::is_same_v>); - static_assert(std::is_default_constructible_v && - std::is_copy_assignable_v); +private: + static constexpr bool kNoexceptCall = std::is_nothrow_copy_constructible_v; + +public: static constexpr MappedType kDefaultValue = DefaultMapValue; static constexpr char kMinChar = static_cast(TrieParams.min_char); static constexpr char kMaxChar = static_cast(TrieParams.max_char); - STRING_MAP_CONSTEVAL StringMapImplManyStrings() noexcept { + STRING_MAP_CONSTEVAL StringMapImplManyStrings() { AddPattern<0, Strings...>(kRootNodeIndex + 1); } - constexpr MappedType operator()(std::nullptr_t) const noexcept = delete; - constexpr MappedType operator()(std::nullptr_t, std::size_t) const noexcept = delete; + constexpr MappedType operator()(std::nullptr_t) const = delete; + constexpr MappedType operator()(std::nullptr_t, std::size_t) const = delete; // clang-format off template [[nodiscard]] ATTRIBUTE_PURE ATTRIBUTE_ALWAYS_INLINE - constexpr MappedType operator()(std::basic_string_view str) const noexcept { + constexpr MappedType operator()(std::basic_string_view str) const noexcept(kNoexceptCall) { // clang-format on - return operator()(str.data(), str.size()); + return (*this)(str.data(), str.size()); } // clang-format off template [[nodiscard]] ATTRIBUTE_PURE ATTRIBUTE_ALWAYS_INLINE - constexpr MappedType operator()(const std::basic_string& str) const noexcept { + constexpr MappedType operator()(const std::basic_string& str) const noexcept(kNoexceptCall) { // clang-format on - return operator()(str.data(), str.size()); + return (*this)(str.data(), str.size()); } // clang-format off template @@ -429,25 +447,25 @@ class [[nodiscard]] StringMapImplManyStrings final { ATTRIBUTE_PURE ATTRIBUTE_ALWAYS_INLINE ATTRIBUTE_SIZED_ACCESS(read_only, 2, 3) - constexpr MappedType operator()(const CharType* str, std::size_t size) const noexcept { + constexpr MappedType operator()(const CharType* str, std::size_t size) const noexcept(kNoexceptCall) { // clang-format on if (std::is_constant_evaluated()) { if constexpr (std::is_same_v) { using IteratorType = InternalIterator; - return operator_call_impl(IteratorType{str}, IteratorType{str + size}); + return op_call_impl(IteratorType{str}, IteratorType{str + size}); } else { using IteratorType = InternalIterator; - return operator_call_impl(IteratorType{str}, IteratorType{str + size}); + return op_call_impl(IteratorType{str}, IteratorType{str + size}); } } else { if constexpr (std::is_same_v) { using IteratorType = InternalIterator; - return operator_call_impl(IteratorType{str}, IteratorType{str + size}); + return op_call_impl(IteratorType{str}, IteratorType{str + size}); } else { using IteratorType = InternalIterator; - return operator_call_impl(IteratorType{str}, IteratorType{str + size}); + return op_call_impl(IteratorType{str}, IteratorType{str + size}); } } } @@ -456,19 +474,18 @@ class [[nodiscard]] StringMapImplManyStrings final { ATTRIBUTE_PURE ATTRIBUTE_ALWAYS_INLINE ATTRIBUTE_ACCESS(read_only, 2) - constexpr MappedType operator()(const char* str) const noexcept { + constexpr MappedType operator()(const char* str) const noexcept(kNoexceptCall) { // clang-format on if (str == nullptr) [[unlikely]] { return kDefaultValue; - } - if (std::is_constant_evaluated()) { + } else if (std::is_constant_evaluated()) { using IteratorType = InternalIterator; using SentinelType = typename IteratorType::CStringSentinel; - return operator_call_impl(IteratorType{str}, SentinelType{}); + return op_call_impl(IteratorType{str}, SentinelType{}); } else { using IteratorType = InternalIterator; using SentinelType = typename IteratorType::CStringSentinel; - return operator_call_impl(IteratorType{str}, SentinelType{}); + return op_call_impl(IteratorType{str}, SentinelType{}); } } @@ -478,9 +495,9 @@ class [[nodiscard]] StringMapImplManyStrings final { [[nodiscard]] ATTRIBUTE_PURE ATTRIBUTE_ALWAYS_INLINE - constexpr MappedType operator()(std::span str) const noexcept { + constexpr MappedType operator()(std::span str) const noexcept(kNoexceptCall) { // clang-format on - return operator()(str.data(), str.size()); + return (*this)(str.data(), str.size()); } #endif @@ -495,17 +512,20 @@ class [[nodiscard]] StringMapImplManyStrings final { std::array edges{}; MappedType node_value = kDefaultValue; }; - std::array nodes_ -#if !(defined(__GNUC__) && defined(__GNUC_MINOR__) && __GNUC__ == 13 && __GNUC_MINOR__ == 1) - // `internal compiler error: Segmentation fault` on gcc 13.1, see - // https://godbolt.org/z/6EWrd8sGG - {} + + using NodesArray = std::array; + +#if GNU_NODES_FIELD_INIT_BUG + // `internal compiler error: Segmentation fault` on gcc 13.1, see + // https://godbolt.org/z/PErqd9PEr + NodesArray nodes_; +#else + NodesArray nodes_{}; #endif - ; template - STRING_MAP_CONSTEVAL void AddPattern(std::size_t first_free_node_index) noexcept { + STRING_MAP_CONSTEVAL void AddPattern(std::size_t first_free_node_index) { std::size_t current_node_index = 0; constexpr std::size_t len = String.size(); for (std::size_t i = 0; i < len; i++) { @@ -534,7 +554,7 @@ class [[nodiscard]] StringMapImplManyStrings final { // clang-format off template ATTRIBUTE_PURE - constexpr MappedType operator_call_impl(IteratorType begin, SentinelIteratorType end) const noexcept { + constexpr MappedType op_call_impl(IteratorType begin, SentinelIteratorType end) const noexcept(kNoexceptCall) { // clang-format on std::size_t current_node_index = kRootNodeIndex; @@ -557,7 +577,7 @@ class [[nodiscard]] StringMapImplManyStrings final { } const auto returned_value = nodes_[current_node_index].node_value; - if constexpr (kMappedTypesInfo.ordered) { + if constexpr (kMappedTypesInfo.trivially_ordered) { if (returned_value != kDefaultValue && (std::less{}(returned_value, kMappedTypesInfo.min_value) || std::greater{}(returned_value, kMappedTypesInfo.max_value))) { @@ -569,7 +589,7 @@ class [[nodiscard]] StringMapImplManyStrings final { } struct TMappedTypesInfo final { - static constexpr bool kMaybeOrdered = + static constexpr bool kMaybeTriviallyOrdered = std::is_arithmetic_v || std::is_pointer_v || std::is_member_pointer_v || (!std::is_polymorphic_v && @@ -584,17 +604,15 @@ class [[nodiscard]] StringMapImplManyStrings final { { std::greater{}(x, y) } -> std::same_as; { std::greater_equal{}(x, y) } -> std::same_as; }); - using DefaultConstructibleSentinelType = - std::conditional_t, MappedType, int>; - bool ordered = false; - DefaultConstructibleSentinelType max_value{}; - DefaultConstructibleSentinelType min_value{}; + bool trivially_ordered = false; + MappedType max_value{}; + MappedType min_value{}; }; - STRING_MAP_CONSTEVAL static TMappedTypesInfo get_mapped_values_info() noexcept { + STRING_MAP_CONSTEVAL static TMappedTypesInfo get_mapped_values_info() noexcept(kNoexceptCall) { TMappedTypesInfo info{}; - if constexpr (TMappedTypesInfo::kMaybeOrdered) { + if constexpr (TMappedTypesInfo::kMaybeTriviallyOrdered) { if constexpr (std::is_floating_point_v) { auto bad_float = [](MappedType x) constexpr noexcept -> bool { return x != x || x >= std::numeric_limits::infinity() || @@ -616,9 +634,9 @@ class [[nodiscard]] StringMapImplManyStrings final { #endif } - info.ordered = true; - info.min_value = MappedValues.front(); - info.max_value = MappedValues.front(); + info.trivially_ordered = true; + info.min_value = MappedValues.front(); + info.max_value = MappedValues.front(); for (const MappedType& value : MappedValues) { if (std::less{}(value, info.min_value)) { info.min_value = value; @@ -634,8 +652,12 @@ class [[nodiscard]] StringMapImplManyStrings final { static constexpr TMappedTypesInfo kMappedTypesInfo = get_mapped_values_info(); }; -template class [[nodiscard]] StringMapImplFewStrings final { static_assert(0 < TrieParams.min_char && TrieParams.min_char <= TrieParams.max_char && @@ -646,87 +668,91 @@ class [[nodiscard]] StringMapImplFewStrings final { public: using MappedType = typename decltype(MappedValues)::value_type; - static_assert(std::is_copy_assignable_v); +private: + static constexpr bool kNoexceptCall = std::is_nothrow_copy_constructible_v; + +public: static constexpr MappedType kDefaultValue = DefaultMapValue; static constexpr char kMinChar = static_cast(TrieParams.min_char); static constexpr char kMaxChar = static_cast(TrieParams.max_char); STRING_MAP_CONSTEVAL StringMapImplFewStrings() noexcept = default; - constexpr MappedType operator()(std::nullptr_t) const noexcept = delete; - constexpr MappedType operator()(std::nullptr_t, std::size_t) const noexcept = delete; + constexpr MappedType operator()(std::nullptr_t) const = delete; + constexpr MappedType operator()(std::nullptr_t, std::size_t) const = delete; // clang-format off - template + template [[nodiscard]] ATTRIBUTE_PURE ATTRIBUTE_ALWAYS_INLINE - constexpr MappedType operator()(std::basic_string_view str) const noexcept { + constexpr MappedType operator()(std::basic_string_view str) const noexcept(kNoexceptCall) { // clang-format on - return operator()(str.data(), str.size()); + return (*this)(str.data(), str.size()); } // clang-format off - template + template [[nodiscard]] ATTRIBUTE_PURE ATTRIBUTE_ALWAYS_INLINE - constexpr MappedType operator()(const std::basic_string& str) const noexcept { + constexpr MappedType operator()(const std::basic_string& str) const noexcept(kNoexceptCall) { // clang-format on - return operator()(str.data(), str.size()); + return (*this)(str.data(), str.size()); } // clang-format off [[nodiscard]] ATTRIBUTE_PURE ATTRIBUTE_ALWAYS_INLINE ATTRIBUTE_ACCESS(read_only, 2) - constexpr MappedType operator()(const char* str) const noexcept { + constexpr MappedType operator()(const char* str) const noexcept(kNoexceptCall) { // clang-format on if (str == nullptr) [[unlikely]] { return kDefaultValue; + } else { + return (*this)(str, std::char_traits::length(str)); } - return operator()(str, std::char_traits::length(str)); } // clang-format off - template + template [[nodiscard]] ATTRIBUTE_PURE ATTRIBUTE_ALWAYS_INLINE ATTRIBUTE_SIZED_ACCESS(read_only, 2, 3) - constexpr MappedType operator()(const CharType* str, std::size_t size) const noexcept { + constexpr MappedType operator()(const CharType* str, std::size_t size) const noexcept(kNoexceptCall) { // clang-format on - return operator_call_impl(str, size); + return op_call_impl(str, size); } #if STRING_MAP_HAS_SPAN // clang-format off - template + template [[nodiscard]] ATTRIBUTE_PURE ATTRIBUTE_ALWAYS_INLINE - constexpr MappedType operator()(std::span str) const noexcept { + constexpr MappedType operator()(std::span str) const noexcept(kNoexceptCall) { // clang-format on - return operator()(str.data(), str.size()); + return (*this)(str.data(), str.size()); } #endif private: // clang-format off - template + template [[nodiscard]] ATTRIBUTE_PURE ATTRIBUTE_ALWAYS_INLINE ATTRIBUTE_SIZED_ACCESS(read_only, 1, 2) - static constexpr MappedType operator_call_impl(const CharType* str, std::size_t size) noexcept { + static constexpr MappedType op_call_impl(const CharType* str, std::size_t size) noexcept(kNoexceptCall) { // clang-format on if (CompString == std::basic_string_view(str, size)) { static_assert(Index < std::size(MappedValues)); return MappedValues[Index]; + } else if constexpr (sizeof...(CompStrings) >= 1) { + return op_call_impl(str, size); + } else { + return kDefaultValue; } - if constexpr (sizeof...(CompStrings) >= 1) { - return operator_call_impl(str, size); - } - return kDefaultValue; } }; @@ -751,7 +777,7 @@ StringMapValues(T, Ts...) namespace strmapdetail { template -STRING_MAP_CONSTEVAL StringMapValues make_index_array() noexcept { +[[nodiscard]] 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) @@ -776,9 +802,13 @@ struct StringHelper, MappedValues, MappedDefaultValue> { 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 MappedType = typename decltype(MappedValues.values)::value_type; + + static_assert( + std::is_same_v>, + "Default value of the StringMap should be of the same type as StringMap values"); + + static_assert(strmapdetail::impl::MappableType); using type = typename std::conditional_t< (sizeof...(Strings) <= 4 && @@ -798,8 +828,7 @@ struct StringHelper, MappedValues, MappedDefaultValue> { #undef STRING_MAP_HAS_BIT #undef STRING_MAP_HAS_SPAN -template +template using StringMap = typename strmapdetail::StringHelper::type; template diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 59470f1..5ad9941 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -719,12 +719,24 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clan list(APPEND TestIsCProject False) endif() -list(APPEND TestFilenames "test_string_switch_map.cpp") -list(APPEND TestDirectories "string-switch-map tests") -list(APPEND TestLangVersions "20 23 26") -list(APPEND TestDependencies "") -list(APPEND TestOptionalDependencies "") -list(APPEND TestIsCProject False) +set(CAN_COMPILE_CONSTEXPR_STRING_MAP True) +if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set(CAN_COMPILE_CONSTEXPR_STRING_MAP CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 13.0) +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(CAN_COMPILE_CONSTEXPR_STRING_MAP CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 11.4) +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + set(CAN_COMPILE_CONSTEXPR_STRING_MAP MSVC_VERSION GREATER_EQUAL 1938) +endif() + +if(CAN_COMPILE_CONSTEXPR_STRING_MAP) + list(APPEND TestFilenames "test_string_switch_map.cpp") + list(APPEND TestDirectories "string-switch-map tests") + list(APPEND TestLangVersions "20 23 26") + list(APPEND TestDependencies "") + list(APPEND TestOptionalDependencies "") + list(APPEND TestIsCProject False) +endif() +unset(CAN_COMPILE_CONSTEXPR_STRING_MAP) list(POP_FRONT TestFilenames) # pop dummy list(POP_FRONT TestDirectories) # pop dummy