diff --git a/.github/workflows/linux.yaml b/.github/workflows/linux.yaml index 262233b..9799d3d 100644 --- a/.github/workflows/linux.yaml +++ b/.github/workflows/linux.yaml @@ -17,17 +17,14 @@ jobs: - compiler: llvm compiler-version: 18 cxx: 23 - - compiler: gcc - compiler-version: 12 - cxx: 20 - compiler: gcc compiler-version: 13 cxx: 20 - compiler: gcc compiler-version: 14 - cxx: 23 + cxx: 23 name: "${{ github.job }} (C++${{ matrix.cxx }}-${{ matrix.compiler }}-${{ matrix.compiler-version }})" - runs-on: ubuntu-24.04 + runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 diff --git a/CMakeLists.txt b/CMakeLists.txt index a8a956d..4d1e71e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,26 +20,41 @@ else() endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - target_compile_options(${PROJECT_NAME} PRIVATE - /W4 + target_compile_options(${PROJECT_NAME} + PRIVATE + /W4 ) else() - target_compile_options(${PROJECT_NAME} PRIVATE - -Wall - -Wextra - -Wuninitialized - -Wno-unused-function - -Wunused-variable + target_compile_options(${PROJECT_NAME} + PRIVATE + -Wall + -Wextra + -Werror + -Wuninitialized + -Wno-unused-function + -Wunused-variable ) endif() -target_include_directories(${PROJECT_NAME} PUBLIC - $ - $ +target_include_directories(${PROJECT_NAME} + PUBLIC + $ + $ ) -target_sources(${PROJECT_NAME} PRIVATE - src/frame_rate_controller.cpp +file(GLOB_RECURSE ORYX_CRT_HEADERS + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + "${CMAKE_CURRENT_SOURCE_DIR}/include/*" +) + +target_sources(${PROJECT_NAME} + PRIVATE + src/frame_rate_controller.cpp + src/uuid.cpp + PUBLIC + FILE_SET HEADERS + BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include" + FILES ${ORYX_CRT_HEADERS} ) @@ -53,8 +68,14 @@ if(ORYX_CRT_BUILD_TESTS) tests/string_split_test.cpp tests/callback_list_test.cpp tests/from_chars_test.cpp + tests/is_one_off_test.cpp + tests/unique_file_ptr_test.cpp + tests/error_group_test.cpp + tests/traits_test.cpp ) + target_link_libraries(${test_exe} PRIVATE ${PROJECT_NAME}) + if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") target_compile_definitions(${test_exe} PUBLIC DOCTEST_CONFIG_USE_STD_HEADERS) endif() @@ -64,35 +85,36 @@ if (ORYX_CRT_INSTALL) include(GNUInstallDirs) include(CMakePackageConfigHelpers) - configure_package_config_file(cmake/${PROJECT_NAME}-config.cmake.in - ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake - INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} + configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/${PROJECT_NAME}-config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" + INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" ) - install( - FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" + write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-version.cmake" + COMPATIBILITY ExactVersion ) - file(GLOB_RECURSE ORYX_CRT_HEADERS RELATIVE ${CMAKE_CURRENT_LIST_DIR} "${CMAKE_CURRENT_LIST_DIR}/include/*" ) - - target_sources(${PROJECT_NAME} - PUBLIC - FILE_SET oryx_headers - TYPE HEADERS - BASE_DIRS $ $ - FILES ${ORYX_CRT_HEADERS} + install( + TARGETS ${PROJECT_NAME} + EXPORT ${PROJECT_NAME}-targets + FILE_SET HEADERS ) install( - TARGETS ${PROJECT_NAME} - EXPORT ${PROJECT_NAME}-exports - FILE_SET oryx_headers DESTINATION ${INCLUDE_INSTALL_DIR} + FILES + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-version.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" + COMPONENT ${PROJECT_NAME} ) install( - EXPORT ${PROJECT_NAME}-exports - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} + EXPORT ${PROJECT_NAME}-targets + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" NAMESPACE oryx:: + FILE ${PROJECT_NAME}-targets.cmake + COMPONENT ${PROJECT_NAME} ) endif () \ No newline at end of file diff --git a/README.md b/README.md index 64320c5..778fce8 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Common C++ Runtime used for personal C++ Projects. Happy to accept any contribut ## Build Locally ```bash -cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -DCMAKE_BUILD_TYPE=Debug -Bbuild -H. +cmake -GNinja -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -DCMAKE_BUILD_TYPE=Debug -Bbuild -H. ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Only needed for clangd ``` diff --git a/include/oryx/argparse.hpp b/include/oryx/argparse.hpp index f4786c0..79e3f8f 100644 --- a/include/oryx/argparse.hpp +++ b/include/oryx/argparse.hpp @@ -46,10 +46,6 @@ class CLI { template void VisitIfContains(std::string_view option, auto visitor) { - if (!Contains(option)) { - return; - } - auto value = GetValue(option); if (value) { std::invoke(visitor, std::forward(*value)); diff --git a/include/oryx/chrono/cycle_timer.hpp b/include/oryx/chrono/cycle_timer.hpp new file mode 100644 index 0000000..4422bf9 --- /dev/null +++ b/include/oryx/chrono/cycle_timer.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +#include +#include + +namespace oryx::chrono { + +template + requires std::chrono::is_clock_v +class CycleTimer { +public: + using Duration = std::chrono::milliseconds; + + explicit CycleTimer(Duration target) + : target_(target) {} + + auto GetNextSleep() -> std::optional { + const auto elapsed = sw_.ElapsedMs(); + if (elapsed >= target_) { + return std::nullopt; + } + + return Duration(target_ - elapsed); + } + + void Reset() { sw_.Reset(); } + + auto target_cycle_time() const -> Duration { return target_; } + +private: + const Duration target_; + details::StopwatchImpl sw_{}; +}; + +template +auto MakeScopedCycleTimerReset(CycleTimer& timer) { + return ScopeExit{[&timer] { timer.Reset(); }}; +} + +template +auto MakeFrameRateTimer(int target_fps) { + return CycleTimer{typename CycleTimer::Duration{1000 / target_fps}}; +} + +} // namespace oryx::chrono \ No newline at end of file diff --git a/include/oryx/chrono/frame_rate_controller.hpp b/include/oryx/chrono/frame_rate_controller.hpp index 53244dc..89fd630 100644 --- a/include/oryx/chrono/frame_rate_controller.hpp +++ b/include/oryx/chrono/frame_rate_controller.hpp @@ -4,13 +4,9 @@ #include -// TODO: Replace this with a CycleTimeController and add a MakeFrameRateController which just calculates the target -// cycle duration like in the current constructor. Then this can also be used to hold a specific cycle time like we -// already to in PathFindingCpp - namespace oryx::chrono { -class FrameRateController { +class [[deprecated("Deprecated in favor of MakeFrameRateTimer() from cycle_timer.hpp")]] FrameRateController { public: explicit FrameRateController(int target_fps); auto Sleep() -> bool; diff --git a/include/oryx/chrono/now.hpp b/include/oryx/chrono/now.hpp new file mode 100644 index 0000000..f451158 --- /dev/null +++ b/include/oryx/chrono/now.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace oryx::chrono { + +using NowClock = std::chrono::system_clock; + +inline auto Now() { return NowClock::now(); } + +inline auto NowMillis() { + using namespace std::chrono; + return duration_cast(Now().time_since_epoch()).count(); +} + +inline auto NowSecs() { + using namespace std::chrono; + return duration_cast(Now().time_since_epoch()).count(); +} + +} // namespace oryx::chrono \ No newline at end of file diff --git a/include/oryx/chrono/stopwatch.hpp b/include/oryx/chrono/stopwatch.hpp index fdd4e49..d57f7ab 100644 --- a/include/oryx/chrono/stopwatch.hpp +++ b/include/oryx/chrono/stopwatch.hpp @@ -2,30 +2,34 @@ #include -#include - namespace oryx::chrono { +namespace details { -class Stopwatch { +template + requires std::chrono::is_clock_v +class StopwatchImpl { public: - using clock = std::chrono::steady_clock; - - Stopwatch() - : start_{clock::now()} {} + StopwatchImpl() + : start_{Clock::now()} {} - Stopwatch(clock::time_point start) + explicit StopwatchImpl(Clock::time_point start) : start_(start) {} - auto Elapsed() const -> std::chrono::duration { return std::chrono::duration(clock::now() - start_); } + auto Elapsed() const -> std::chrono::nanoseconds { return Clock::now() - start_; } auto ElapsedMs() const -> std::chrono::milliseconds { - return std::chrono::duration_cast(clock::now() - start_); + return std::chrono::duration_cast(Clock::now() - start_); } - void Reset() { start_ = clock::now(); } + void Reset() { start_ = Clock::now(); } - auto GetStart() const -> clock::time_point { return start_; } + auto GetStart() const { return start_; } private: - clock::time_point start_; + Clock::time_point start_; }; +} // namespace details + +using HighResolutionStopwatch = details::StopwatchImpl; +using Stopwatch = details::StopwatchImpl; + } // namespace oryx::chrono \ No newline at end of file diff --git a/include/oryx/enchantum.hpp b/include/oryx/enchantum.hpp index b1b5751..aef7e14 100644 --- a/include/oryx/enchantum.hpp +++ b/include/oryx/enchantum.hpp @@ -31,7 +31,12 @@ constexpr string_view extract_name_from_type_name(const string_view type_name) n template constexpr auto raw_type_name_func() noexcept { -#if defined(__clang__) +#if defined(__NVCOMPILER) + constexpr std::size_t prefix = 0; + constexpr auto s = string_view( + __PRETTY_FUNCTION__ + SZC("constexpr auto enchantum::details::raw_type_name_func() noexcept [with T = "), + SZC(__PRETTY_FUNCTION__) - SZC("constexpr auto enchantum::details::raw_type_name_func() noexcept [with T = ]")); +#elif defined(__clang__) constexpr std::size_t prefix = 0; constexpr auto s = string_view(__PRETTY_FUNCTION__ + SZC("auto enchantum::details::raw_type_name_func() [_ = "), @@ -70,6 +75,8 @@ constexpr auto type_name_func() noexcept { "pointers"); constexpr auto& array = raw_type_name_func_var; + static_assert(array[array.size() - 2] != '>', "enchantum::type_name does not work well with a templated type"); + constexpr auto s = details::extract_name_from_type_name(string_view(array.data(), array.size() - 1)); std::array ret{}; for (std::size_t i = 0; i < s.size(); ++i) ret[i] = s[i]; @@ -127,7 +134,9 @@ using ::std::string; } // namespace enchantum -#include +#ifdef __cpp_concepts + #include +#endif #include #include #include @@ -153,9 +162,35 @@ using ::std::string; namespace enchantum { +template > +inline constexpr bool is_scoped_enum = false; + +template +inline constexpr bool is_scoped_enum = !std::is_convertible_v>; + +template +inline constexpr bool is_unscoped_enum = std::is_enum_v && !is_scoped_enum; + +template +inline constexpr bool has_fixed_underlying_type = false; + +template +inline constexpr bool has_fixed_underlying_type = std::is_enum_v; + +#ifdef __cpp_concepts + template concept Enum = std::is_enum_v; +template +inline constexpr bool is_bitflag = requires(E e) { + requires std::same_as || std::same_as; + { ~e } -> std::same_as; + { e | e } -> std::same_as; + { e &= e } -> std::same_as; + { e |= e } -> std::same_as; +}; + template concept SignedEnum = Enum && std::signed_integral>; @@ -171,75 +206,146 @@ concept UnscopedEnum = Enum && !ScopedEnum; template concept EnumOfUnderlying = Enum && std::same_as, Underlying>; -template -inline constexpr bool is_bitflag = requires(E e) { - requires std::same_as || std::same_as; - { ~e } -> std::same_as; - { e | e } -> std::same_as; - { e &= e } -> std::same_as; - { e |= e } -> std::same_as; -}; - template concept BitFlagEnum = Enum && is_bitflag; template concept EnumFixedUnderlying = Enum && requires { T{0}; }; -template -struct enum_traits; +#else -template -struct enum_traits { -private: - using U = std::underlying_type_t; - using L = std::numeric_limits; +template +inline constexpr bool is_bitflag = false; -public: - static constexpr std::size_t prefix_length = 0; +// clang-format off +template +inline constexpr bool is_bitflag() &= E{}), + decltype(std::declval() |= E{}) + >> = std::is_enum_v + && (std::is_same_v || std::is_same_v) + && std::is_same_v + && std::is_same_v + && std::is_same_v() &= E{}), E&> + && std::is_same_v() |= E{}), E&> + ; +// clang-format on +#endif - static constexpr auto min = (L::min)() > ENCHANTUM_MIN_RANGE ? (L::min)() : ENCHANTUM_MIN_RANGE; - static constexpr auto max = (L::max)() < ENCHANTUM_MAX_RANGE ? (L::max)() : ENCHANTUM_MAX_RANGE; -}; +namespace details { +template +constexpr auto Max(T a, U b) { + return a < b ? b : a; +} +template +constexpr auto Min(T a, U b) { + return a > b ? b : a; +} +#if !defined(__NVCOMPILER) && defined(__clang__) && __clang_major__ >= 20 +template +inline constexpr bool is_valid_cast = false; -template -struct enum_traits { -private: +template +inline constexpr bool is_valid_cast(V)>>> = true; + +template range, decltype(range) old_range> +constexpr auto valid_cast_range_recurse() noexcept { + // this tests whether `static_cast`ing range is valid + // because C style enums stupidly is like a bit field + // `enum E { a,b,c,d = 3};` is like a bitfield `struct E { int val : 2;}` + // which means giving E.val a larger than 2 bit value is UB so is it for enums + // and gcc and msvc ignore this (for good) + // while clang makes it a subsituation failure which we can check for + // using std::inegral_constant makes sure this is a constant expression situation + // for SFINAE to occur + if constexpr (is_valid_cast) + return valid_cast_range_recurse(); + else + return old_range > 0 ? old_range * 2 - 1 : old_range; +} +template +constexpr auto valid_cast_range() noexcept { using T = std::underlying_type_t; using L = std::numeric_limits; -public: - static constexpr std::size_t prefix_length = 0; + if constexpr (max_range == 0) + return T{0}; + else if constexpr (max_range > 0 && is_valid_cast) + return L::max(); + else if constexpr (max_range < 0 && is_valid_cast) + return L::min(); + else + return details::valid_cast_range_recurse(); +} - static constexpr auto min = []() { - if constexpr (std::is_same_v) - return false; - else - return (ENCHANTUM_MIN_RANGE) < 0 ? 0 : (ENCHANTUM_MIN_RANGE); - }(); - static constexpr auto max = []() { - if constexpr (std::is_same_v) - return true; - else - return (L::max)() < (ENCHANTUM_MAX_RANGE) ? (L::max)() : (ENCHANTUM_MAX_RANGE); - }(); +#endif + +template +constexpr auto enum_range_of(const int max_range) { + using T = std::underlying_type_t; + if constexpr (std::is_same_v) { + return max_range > 0; + } else { + using L = std::numeric_limits; +#if !defined(__NVCOMPILER) && defined(__clang__) && __clang_major__ >= 20 + constexpr auto Max = has_fixed_underlying_type ? (L::max)() : details::valid_cast_range(); + constexpr auto Min = + has_fixed_underlying_type ? (L::min)() : details::valid_cast_range ? -1 : 0>(); +#else + constexpr auto Max = (L::max)(); + constexpr auto Min = (L::min)(); +#endif + (void)Min; // Only used in signed branch + if constexpr (std::is_signed_v) { + return max_range > 0 ? details::Min(ENCHANTUM_MAX_RANGE, Max) : details::Max(ENCHANTUM_MIN_RANGE, Min); + } else { + return max_range > 0 ? details::Min(static_cast(ENCHANTUM_MAX_RANGE), Max) : 0; + } + } +} +} // namespace details + +template +struct enum_traits { +private: + using T = std::underlying_type_t; + +public: + using zxshady_enchantum_is_not_specialized_tag = void; + static constexpr auto max = details::enum_range_of(1); + static constexpr decltype(max) min = details::enum_range_of(-1); }; +namespace details { +template +inline constexpr bool has_specialized_traits = true; +template +inline constexpr bool has_specialized_traits::zxshady_enchantum_is_not_specialized_tag> = + false; + +} // namespace details + } // namespace enchantum + +#ifdef __cpp_concepts + #define ENCHANTUM_DETAILS_ENUM_CONCEPT(Name) Enum Name + #define ENCHANTUM_DETAILS_ENUM_BITFLAG_CONCEPT(Name) BitFlagEnum Name +#else + #define ENCHANTUM_DETAILS_ENUM_CONCEPT(Name) typename Name, std::enable_if_t, int> = 0 + #define ENCHANTUM_DETAILS_ENUM_BITFLAG_CONCEPT(Name) typename Name, std::enable_if_t, int> = 0 +#endif #include #include #include +#include #include -namespace enchantum::details { -template -constexpr T Max(T a, T b) { - return a < b ? b : a; -} -template -constexpr T Min(T a, T b) { - return a > b ? b : a; -} +namespace enchantum { +namespace details { template inline constexpr std::size_t prefix_length_or_zero = 0; @@ -248,36 +354,21 @@ template inline constexpr auto prefix_length_or_zero::prefix_length)> = std::size_t{enum_traits::prefix_length}; -template -constexpr auto generate_arrays() { - if constexpr (BitFlagEnum) { - constexpr std::size_t bits = sizeof(Enum) * CHAR_BIT; - std::array ret{}; // 0 value reflected - for (std::size_t i = 0; i < bits; ++i) - ret[i + 1] = static_cast(static_cast>>(1) << i); - - return ret; - } else { - static_assert(Min < Max, "enum_traits::min must be less than enum_traits::max"); - std::array array{}; - auto* const array_data = array.data(); - for (std::size_t i = 0, size = Max - Min + 1; i < size; ++i) - array_data[i] = static_cast(static_cast(i) + Min); - return array; - } -} - -} // namespace enchantum::details - -#if defined(__clang__) +template +struct ReflectStringReturnValue { + Underlying values[ArraySize]{}; + std::uint8_t string_lengths[ArraySize]{}; + // the sum of all character names must be less than the size of this array + // no one will likely hit this unless you for some odd reason have extremely long names + char strings[1024 * 8]{}; + std::size_t total_string_length = 0; + std::size_t valid_count = 0; +}; -// Clang <= 12 outputs "NUMBER" if casting -// Clang > 12 outputs "(E)NUMBER". +} // namespace details +} // namespace enchantum - #if defined __has_warning && __has_warning("-Wenum-constexpr-conversion") - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wenum-constexpr-conversion" - #endif +#if defined(__NVCOMPILER) #include #include @@ -285,247 +376,259 @@ constexpr auto generate_arrays() { #include #include #include - namespace enchantum { - #if __clang_major__ >= 20 namespace details { +constexpr std::size_t find_semicolon(const char* s) { + for (std::size_t i = 0; true; ++i) + if (s[i] == ';') return i; +} +constexpr std::size_t enum_in_array_name_size(const string_view raw_type_name, const bool is_scoped_enum) noexcept { + if (is_scoped_enum) return raw_type_name.size(); -template -inline constexpr bool is_valid_cast = false; + if (const auto pos = raw_type_name.rfind(':'); pos != string_view::npos) return pos - 1; + return 0; +} -template -inline constexpr bool is_valid_cast(V)>>> = true; + #define SZC(x) (sizeof(x) - 1) -template range, decltype(range) old_range> -constexpr auto valid_cast_range_recurse() noexcept { - // this tests whether `static_cast`ing range is valid - // because C style enums stupidly is like a bit field - // `enum E { a,b,c,d = 3};` is like a bitfield `struct E { int val : 2;}` - // which means giving E.val a larger than 2 bit value is UB so is it for enums - // and gcc and msvc ignore this (for good) - // while clang makes it a subsituation failure which we can check for - // using std::inegral_constant makes sure this is a constant expression situation - // for SFINAE to occur - if constexpr (is_valid_cast) - return valid_cast_range_recurse(); - else - return old_range > 0 ? old_range * 2 - 1 : old_range; +template +constexpr auto var_name() noexcept { + return __PRETTY_FUNCTION__ + SZC("constexpr auto enchantum::details::var_name() noexcept [with _ *V = (_ *)0; "); } -template max_range = 1> -constexpr auto valid_cast_range() { - using L = std::numeric_limits; - if constexpr (max_range > 0 && is_valid_cast) - return L::max(); - else if constexpr (max_range < 0 && is_valid_cast) - return L::min(); - else - return valid_cast_range_recurse(); + +template +constexpr void parse_string(const char* str, + const std::size_t least_length_when_casting, + const std::size_t least_length_when_value, + const IntType min, + const std::size_t array_size, + const bool null_terminated, + IntType* const values, + std::uint8_t* const string_lengths, + char* const strings, + std::size_t& total_string_length, + std::size_t& valid_count) { + for (std::size_t index = 0; index < array_size; ++index) { + // check if cast (starts with '(') + str += SZC("_ *V = "); + if (str[0] == '(') { + str += least_length_when_casting; + while (*str++ != ';') /*intentionally empty*/ + ; + str += SZC(" "); + } else { + str += least_length_when_value; + const auto commapos = details::find_semicolon(str); + if constexpr (IsBitFlag) + values[valid_count] = index == 0 ? IntType{} : static_cast(IntType{1} << (index - 1)); + else + values[valid_count] = static_cast(min + static_cast(index)); + string_lengths[valid_count++] = static_cast(commapos); + __builtin_memcpy(strings + total_string_length, str, commapos); + total_string_length += commapos + null_terminated; + str += commapos + SZC("; "); + } + } } +template +constexpr auto reflect(std::index_sequence) noexcept { + using MinT = decltype(Min); + using T = std::underlying_type_t; + + constexpr auto elements_local = []() { + constexpr auto ArraySize = sizeof...(Is) + is_bitflag; + #pragma diag_suppress implicit_return_from_non_void_function + const auto str = [](auto dependant) { + constexpr bool always_true = sizeof(dependant) != 0; + // forces NVCC to shorten the string types + struct _ {}; + // using a pointer since C++17 only allows pointers to class types not the class types themselves + constexpr _* A{}; + using Underlying = std::make_unsigned_t, unsigned char, T>>; + // dummy 0 + if constexpr (always_true && is_bitflag) // sizeof... to make contest dependant + return details::var_name(!always_true), static_cast(Underlying(1) << Is)..., 0>(); + else + return details::var_name(static_cast(Is) + Min)..., int(!always_true)>(); + }(0); + #pragma diag_default implicit_return_from_non_void_function + + constexpr auto enum_in_array_len = details::enum_in_array_name_size(raw_type_name, is_scoped_enum); + // Ubuntu Clang 20 complains about using local constexpr variables in a local struct + ReflectStringReturnValue, ArraySize> ret; + + // ((anonymous namespace)::A)0 + // (anonymous namespace)::a + // this is needed to determine whether the above are cast expression if 2 braces are + // next to eachother then it is a cast but only for anonymoused namespaced enums + + details::parse_string>( + /*str = */ str, + /*least_length_when_casting=*/SZC("(") + enum_in_array_len + SZC(")0"), + /*least_length_when_value=*/details::prefix_length_or_zero + + (enum_in_array_len != 0 ? enum_in_array_len + SZC("::") : 0), + /*min = */ static_cast(Min), + /*array_size = */ ArraySize, + /*null_terminated= */ NullTerminated, + /*enum_values= */ ret.values, + /*string_lengths= */ ret.string_lengths, + /*strings= */ ret.strings, + /*total_string_length*/ ret.total_string_length, + /*valid_count*/ ret.valid_count); + + return ret; + }(); + + using Strings = std::array; + + struct { + decltype(elements_local) elements; + Strings strings{}; + } data = {elements_local}; + __builtin_memcpy(data.strings.data(), elements_local.strings, data.strings.size()); + return data; } // namespace details -template - requires SignedEnum && (!EnumFixedUnderlying) -struct enum_traits { -private: - using T = std::underlying_type_t; +} // namespace details +} // namespace enchantum +#elif defined(__clang__) -public: - static constexpr auto max = details::Min(details::valid_cast_range(), static_cast(ENCHANTUM_MAX_RANGE)); - static constexpr decltype(max) min = - details::Max(details::valid_cast_range(), static_cast(ENCHANTUM_MIN_RANGE)); -}; +// Clang <= 12 outputs "NUMBER" if casting +// Clang > 12 outputs "(E)NUMBER". -template - requires UnsignedEnum && (!EnumFixedUnderlying) -struct enum_traits { - static constexpr auto max = - details::Min(details::valid_cast_range(), static_cast>(ENCHANTUM_MAX_RANGE)); - static constexpr decltype(max) min = 0; -}; + #if defined __has_warning + #if __has_warning("-Wenum-constexpr-conversion") + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wenum-constexpr-conversion" + #endif #endif + #include + #include + #include + #include + #include + #include + +namespace enchantum { + namespace details { - #define SZC(x) (sizeof(x) - 1) -template -constexpr auto enum_in_array_name() noexcept { - #if __clang_major__ <= 12 - using E = decltype(Enum); - if constexpr (std::is_convertible_v>) { - if (const auto pos = raw_type_name.rfind(':'); pos != string_view::npos) - return raw_type_name.substr(0, pos - 1); - return string_view(); - } else { - return raw_type_name; - } - #else - // constexpr auto f() [with auto _ = ( - // constexpr auto f() [Enum = (Scoped)0] - auto s = string_view(__PRETTY_FUNCTION__ + SZC("auto enchantum::details::enum_in_array_name() [Enum = "), - SZC(__PRETTY_FUNCTION__) - SZC("auto enchantum::details::enum_in_array_name() [Enum = ]")); +constexpr auto enum_in_array_name(const string_view raw_type_name, const bool is_scoped_enum) noexcept { + if (is_scoped_enum) return raw_type_name; - if constexpr (ScopedEnum) { - if (s[s.size() - 2] == ')') { - s.remove_prefix(SZC("(")); - s.remove_suffix(SZC(")0")); - return s; - } else { - return s.substr(0, s.rfind("::")); - } - } else { - if (s.size() != 1 && s[s.size() - 2] == ')') { - s.remove_prefix(SZC("(")); - s.remove_suffix(SZC(")0")); - } - if (const auto pos = s.rfind(':'); pos != s.npos) return s.substr(0, pos - 1); - return string_view(); - } - #endif + if (const auto pos = raw_type_name.rfind(':'); pos != string_view::npos) return raw_type_name.substr(0, pos - 1); + return string_view(); } + #define SZC(x) (sizeof(x) - 1) + template constexpr auto var_name() noexcept { // "auto enchantum::details::var_name() [Vs = <(A)0, a, b, c, e, d, (A)6>]" - constexpr auto funcsig_off = SZC("auto enchantum::details::var_name() [Vs = <"); - return string_view(__PRETTY_FUNCTION__ + funcsig_off, SZC(__PRETTY_FUNCTION__) - funcsig_off - SZC(">]")); + return __PRETTY_FUNCTION__ + SZC("auto enchantum::details::var_name() [Vs = <"); } - #if __clang_major__ <= 11 -template -inline constexpr auto static_storage_for_chars = std::array{Chars...}; +template +constexpr void parse_string(std::size_t index_check, + const char* str, + const std::size_t least_length_when_casting, + const std::size_t least_length_when_value, + const IntType min, + const std::size_t array_size, + const bool null_terminated, + IntType* const values, + std::uint8_t* const string_lengths, + char* const strings, + std::size_t& total_string_length, + std::size_t& valid_count) { + (void)index_check; + for (std::size_t index = 0; index < array_size; ++index) { + #if __clang_major__ > 12 + // check if cast (starts with '(') + if (str[index_check] == '(') #else -template -inline constexpr auto static_storage_for = Copy; + // check if it is a number or negative sign + if (str[0] == '-' || (str[0] >= '0' && str[0] <= '9')) #endif -template -constexpr auto reflect() noexcept { - constexpr auto Min = enum_traits::min; - constexpr auto Max = enum_traits::max; - constexpr auto bits = []() { - #if __clang_major__ < 20 - return (sizeof(E) * CHAR_BIT) - std::is_signed_v; - #else - if constexpr (EnumFixedUnderlying) { - return (sizeof(E) * CHAR_BIT) - std::is_signed_v; + { + str = __builtin_char_memchr(str + least_length_when_casting, ',', UINT8_MAX) + SZC(", "); } else { - auto v = valid_cast_range(); - std::size_t r = 1; - while (v >>= 1) r++; - return r; - } - #endif - }(); - constexpr auto elements = []() { - using Under = std::underlying_type_t; - using Underlying = std::make_unsigned_t, unsigned char, Under>>; - constexpr auto ArraySize = is_bitflag ? 1 + bits : (Max - Min) + 1; - constexpr auto ConstStr = [](std::index_sequence) { - if constexpr (sizeof...(Idx) && is_bitflag) // sizeof... to make contest dependant - return details::var_name(Underlying(1) << Idx)...>(); + str += least_length_when_value; + const auto commapos = static_cast(__builtin_char_memchr(str, ',', UINT8_MAX) - str); + if constexpr (IsBitFlag) + values[valid_count] = index == 0 ? IntType{} : static_cast(IntType{1} << (index - 1)); else - return details::var_name(static_cast(Idx) + Min)...>(); - }(std::make_index_sequence < is_bitflag ? bits : ArraySize > ()); + values[valid_count] = static_cast(min + static_cast(index)); + string_lengths[valid_count++] = static_cast(commapos); + __builtin_memcpy(strings + total_string_length, str, commapos); + total_string_length += commapos + null_terminated; + str += commapos + SZC(", "); + } + } +} - auto str = ConstStr; +template +constexpr auto reflect(std::index_sequence) noexcept { + using MinT = decltype(Min); + using T = std::underlying_type_t; + using Underlying = std::make_unsigned_t, unsigned char, T>>; + + constexpr auto elements_local = []() { + constexpr auto ArraySize = sizeof...(Is) + is_bitflag; + const auto str = [](auto dependant) { + constexpr bool always_true = sizeof(dependant) != 0; + // dummy 0 + if constexpr (always_true && is_bitflag) // sizeof... to make contest dependant + { + return details::var_name(!always_true), static_cast(Underlying(1) << Is)..., 0>(); + } else { + return details::var_name(static_cast(Is) + Min)..., int(!always_true)>(); + } + }(0); - constexpr auto enum_in_array_name = details::enum_in_array_name(); + constexpr auto enum_in_array_name = details::enum_in_array_name(raw_type_name, is_scoped_enum); constexpr auto enum_in_array_len = enum_in_array_name.size(); // Ubuntu Clang 20 complains about using local constexpr variables in a local struct - using CharArray = char[ConstStr.size() + (NullTerminated * ArraySize)]; - struct RetVal { - struct ElemenetPair { - E value; - std::uint8_t len; - }; - ElemenetPair pairs[ArraySize]{}; - CharArray strings{}; - std::size_t total_string_length = 0; - std::size_t valid_count = 0; - } ret; + ReflectStringReturnValue, ArraySize> ret; - // ((anonymous namespace)::A)0 - // (anonymous namespace)::a + // ((anonymous namespace)::A)0 + // (anonymous namespace)::a + // this is needed to determine whether the above are cast expression if 2 braces are + // next to eachother then it is a cast but only for anonymoused namespaced enums + constexpr std::size_t index_check = enum_in_array_name.size() != 0 && enum_in_array_name[0] == '(' ? 1 : 0; - // this is needed to determine whether the above are cast expression if 2 braces are - // next to eachother then it is a cast but only for anonymoused namespaced enums + details::parse_string>( + /*index_check=*/index_check, + /*str = */ str, #if __clang_major__ > 12 - constexpr std::size_t index_check = !enum_in_array_name.empty() && enum_in_array_name.front() == '(' ? 1 : 0; - #endif - for (std::size_t index = 0; index < ArraySize; ++index) { - #if __clang_major__ > 12 - // check if cast (starts with '(') - if (str[index_check] == '(') + /*least_length_when_casting=*/SZC("(") + enum_in_array_len + SZC(")0"), #else - // check if it is a number or negative sign - if (str[0] == '-' || (str[0] >= '0' && str[0] <= '9')) + /*least_length_when_casting=*/1, #endif - { - #if __clang_major__ > 12 - str.remove_prefix(SZC("(") + enum_in_array_len + SZC(")0")); // there is atleast 1 base 10 digit - #endif - // https://clang.llvm.org/docs/LanguageExtensions.html#string-builtins - // char* __builtin_char_memchr(const char* haystack, int needle, size_t size); - if (const auto* commapos = __builtin_char_memchr(str.data(), ',', str.size()); commapos) - str.remove_prefix(static_cast(commapos - str.data()) + SZC(", ")); - } else { - if constexpr (enum_in_array_len != 0) { - str.remove_prefix(enum_in_array_len + SZC("::")); - } - - if constexpr (details::prefix_length_or_zero != 0) { - str.remove_prefix(details::prefix_length_or_zero); - } - - const auto* commapos_ = __builtin_char_memchr(str.data(), ',', str.size()); - - const auto commapos = commapos_ ? std::size_t(commapos_ - str.data()) : str.npos; - - const auto name = str.substr(0, commapos); - - { - const auto name_size = static_cast(name.size()); - if constexpr (is_bitflag) - ret.pairs[ret.valid_count++] = {index == 0 ? E() : E(Underlying{1} << (index - 1)), name_size}; - else - ret.pairs[ret.valid_count++] = {E(Min + static_cast(index)), name_size}; + /*least_length_when_value=*/details::prefix_length_or_zero + + (enum_in_array_len != 0 ? enum_in_array_len + SZC("::") : 0), + /*min = */ static_cast(Min), + /*array_size = */ ArraySize, + /*null_terminated= */ NullTerminated, + /*enum_values= */ ret.values, + /*string_lengths= */ ret.string_lengths, + /*strings= */ ret.strings, + /*total_string_length*/ ret.total_string_length, + /*valid_count*/ ret.valid_count); - __builtin_memcpy(ret.strings + ret.total_string_length, name.data(), name_size); - ret.total_string_length += name_size + NullTerminated; - } - if (commapos != str.npos) str.remove_prefix(commapos + SZC(", ")); - } - } return ret; }(); - std::array ret; - { - // intentionally >= 12, clang 11 does not support class non type template parameters - #if __clang_major__ >= 12 - constexpr auto strings = [](const auto total_length, const char* data) { - std::array strings; - __builtin_memcpy(strings.data(), data, total_length.value); - return strings; - }(std::integral_constant{}, elements.strings); - constexpr const auto* str = static_storage_for.data(); - #else - constexpr const auto* str = [elements](std::index_sequence) { - return static_storage_for_chars.data(); - }(std::make_index_sequence{}); - #endif + using Strings = std::array; - auto* const ret_data = ret.data(); - for (std::size_t i = 0, string_index = 0; i < elements.valid_count; ++i) { - const auto [e, length] = elements.pairs[i]; - auto& [re, rs] = ret_data[i]; - using StringView = std::decay_t; - re = e; - rs = StringView{str + string_index, length}; - string_index += length + NullTerminated; - } - } - return ret; + struct { + decltype(elements_local) elements; + Strings strings{}; + } data = {elements_local}; + __builtin_memcpy(data.strings.data(), elements_local.strings, data.strings.size()); + return data; } // namespace details } // namespace details @@ -535,8 +638,10 @@ constexpr auto reflect() noexcept { } // namespace enchantum - #if defined __has_warning && __has_warning("-Wenum-constexpr-conversion") - #pragma clang diagnostic pop + #if defined __has_warning + #if __has_warning("-Wenum-constexpr-conversion") + #pragma clang diagnostic pop + #endif #endif #undef SZC #elif defined(__GNUC__) || defined(__GNUG__) @@ -548,9 +653,17 @@ constexpr auto reflect() noexcept { #include #include + #define ENCAHNTUM_DETAILS_GCC_MAJOR __GNUC__ + #if __GNUC__ <= 10 + // for out of bounds conversions for C style enums + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wconversion" + #endif + namespace enchantum { namespace details { #define SZC(x) (sizeof(x) - 1) + // this is needed since gcc transforms "{anonymous}" into "" for values template constexpr auto enum_in_array_name_size() noexcept { @@ -563,9 +676,9 @@ constexpr auto enum_in_array_name_size() noexcept { using E = decltype(Enum); // if scoped if constexpr (!std::is_convertible_v>) { - return s.front() == '(' ? s.size() - SZC("()0") : s.rfind(':') - 1; + return s[0] == '(' ? s.size() - SZC("()0") : s.rfind(':') - 1; } else { - if (s.front() == '(') { + if (s[0] == '(') { s.remove_prefix(SZC("(")); s.remove_suffix(SZC(")0")); } @@ -574,370 +687,891 @@ constexpr auto enum_in_array_name_size() noexcept { } } + #if __GNUC__ == 10 +template +constexpr auto gcc10_workaround() noexcept { + using E = decltype(V); + using T = std::underlying_type_t; + constexpr auto prefix = SZC("constexpr auto enchantum::details::gcc10_workaround() [with auto V = "); + constexpr auto begin = __PRETTY_FUNCTION__ + prefix; + if constexpr (begin[0] == '(') { + std::size_t i = SZC(__PRETTY_FUNCTION__) - prefix - SZC("("); + const char* end = __PRETTY_FUNCTION__ + SZC(__PRETTY_FUNCTION__) - 1; + while (*end != ')') { + --end; + --i; + } + --i; + return i; + } else if constexpr (static_cast(V) == (std::numeric_limits::max)()) { + constexpr auto s = details::enum_in_array_name_size(); + constexpr auto& tyname = raw_type_name; + if (constexpr auto pos = tyname.rfind("::"); pos != tyname.npos) { + return s + tyname.substr(pos).size(); + } else { + return s + tyname.size(); + } + } else { + return details::gcc10_workaround(static_cast(V) + 1)>(); + } +} + #endif + template constexpr auto length_of_enum_in_template_array_if_casting() noexcept { - if constexpr (ScopedEnum) { + if constexpr (is_scoped_enum) { return details::enum_in_array_name_size(); } else { - constexpr auto s = details::enum_in_array_name_size(); - constexpr auto& tyname = raw_type_name; - if (constexpr auto pos = tyname.rfind("::"); pos != tyname.npos) { - return s + tyname.substr(pos).size(); - } else { - return s + tyname.size(); - } + #if __GNUC__ == 10 + return details::gcc10_workaround( + (std::numeric_limits>::min)())>(); + #else + constexpr auto s = details::enum_in_array_name_size(); + constexpr auto& tyname = raw_type_name; + if (constexpr auto pos = tyname.rfind("::"); pos != tyname.npos) { + return s + tyname.substr(pos).size(); + } else { + return s + tyname.size(); + } + #endif + } +} + +template +constexpr auto var_name() noexcept { + return __PRETTY_FUNCTION__ + SZC("constexpr auto enchantum::details::var_name() [with auto ...Vs = {"); +} + +template +constexpr void parse_string(const char* str, + const std::size_t least_length_when_casting, + const std::size_t least_length_when_value, + const IntType min, + const std::size_t array_size, + const bool null_terminated, + IntType* const values, + std::uint8_t* const string_lengths, + char* const strings, + std::size_t& total_string_length, + std::size_t& valid_count) { + (void)min; // not always used + for (std::size_t index = 0; index < array_size; ++index) { + if (*str == '(') { + str = std::char_traits::find(str + least_length_when_casting, UINT8_MAX, ',') + SZC(", "); + } else { + str += least_length_when_value; + // although gcc implementation of std::char_traits::find is using a for loop internally + // copying the code of the function makes it way slower to compile, this was surprising. + const auto commapos = static_cast(std::char_traits::find(str, UINT8_MAX, ',') - str); + if constexpr (IsBitFlag) + values[valid_count] = index == 0 ? IntType{} : static_cast(IntType{1} << (index - 1)); + else + values[valid_count] = static_cast(min + static_cast(index)); + string_lengths[valid_count++] = static_cast(commapos); + for (std::size_t i = 0; i < commapos; ++i) strings[total_string_length++] = str[i]; + total_string_length += null_terminated; + str += commapos + SZC(", "); + } + } +} + +template +constexpr auto reflect(std::index_sequence) noexcept { + constexpr auto elements_local = []() { + constexpr auto ArraySize = sizeof...(Is) + is_bitflag; + using Under = std::underlying_type_t; + using Underlying = std::make_unsigned_t, unsigned char, Under>>; + + constexpr auto str = [](const auto dependant) { + #if __GNUC__ <= 10 + // GCC 10 does not have it + #define CAST(type, value) static_cast(value) + #else + // __builtin_bit_cast used to silence errors when casting out of unscoped enums range + #define CAST(type, value) __builtin_bit_cast(type, value) + #endif + // dummy 0 + if constexpr (sizeof(dependant) && is_bitflag) // sizeof... to make contest dependant + return details::var_name(Underlying{1} << Is))..., 0>(); + else + return details::var_name(static_cast(Is) + Min))..., 0>(); + #undef CAST + }(0); + + constexpr auto enum_in_array_len = details::enum_in_array_name_size(); + constexpr auto length_of_enum_in_template_array_casting = + details::length_of_enum_in_template_array_if_casting(); + + ReflectStringReturnValue, ArraySize> ret; + details::parse_string>( + /*str = */ str, + /*least_length_when_casting=*/SZC("(") + length_of_enum_in_template_array_casting + SZC(")0"), + /*least_length_when_value=*/details::prefix_length_or_zero + + (enum_in_array_len != 0 ? enum_in_array_len + SZC("::") : 0), + /*min = */ static_cast>(Min), + /*array_size = */ ArraySize, + /*null_terminated= */ NullTerminated, + /*enum_values= */ ret.values, + /*string_lengths= */ ret.string_lengths, + /*strings= */ ret.strings, + /*total_string_length*/ ret.total_string_length, + /*valid_count*/ ret.valid_count); + return ret; + }(); + using Strings = std::array; + + struct { + decltype(elements_local) elements; + Strings strings{}; + } data = {elements_local}; + const auto size = data.strings.size(); + auto* const data_string = data.strings.data(); + for (std::size_t i = 0; i < size; ++i) data_string[i] = elements_local.strings[i]; + return data; +} + +} // namespace details + +} // namespace enchantum + + #undef SZC + + #if __GNUC__ <= 10 + #pragma GCC diagnostic pop + #endif + +#elif defined(_MSC_VER) + + #include + #include + #include + #include + #include + #include + + // This macro controls the compile time optimization of msvc + // This macro may break some enums with very large enum ranges selected. + // **may** as in I have not found a case where it does + // but it speeds up compilation massivly. + // from 20 secs to 14.6 secs + // from 119 secs to 85 + #ifndef ENCHANTUM_ENABLE_MSVC_SPEEDUP + #define ENCHANTUM_ENABLE_MSVC_SPEEDUP 1 + #endif +namespace enchantum { + + #define SZC(x) (sizeof(x) - 1) +namespace details { + +template +constexpr auto enum_in_array_name_size() noexcept { + auto s = string_view{ + __FUNCSIG__ + SZC("auto __cdecl enchantum::details::enum_in_array_name_size<"), + SZC(__FUNCSIG__) - SZC("auto __cdecl enchantum::details::enum_in_array_name_size<>(void) noexcept")}; + + if constexpr (is_scoped_enum) { + if (s[0] == '(') { + s.remove_prefix(SZC("(enum ")); + s.remove_suffix(SZC(")0x0")); + return s.size(); + } + return s.substr(0, s.rfind(':') - 1).size(); + } else { + if (s[0] == '(') { + s.remove_prefix(SZC("(enum ")); + s.remove_suffix(SZC(")0x0")); + } + if (const auto pos = s.rfind(':'); pos != s.npos) return pos - 1; + return std::size_t(0); + } +} + +template +constexpr auto __cdecl var_name() noexcept { + // auto __cdecl f{enum + // `anonymous-namespace'::UnscopedAnon + return __FUNCSIG__ + SZC("auto __cdecl enchantum::details::var_name<"); +} + +template +constexpr void parse_string(const char* str, + const std::size_t least_length_when_casting, + const std::size_t least_length_when_value, + const IntType min, + const std::size_t array_size, + const bool null_terminated, + IntType* const values, + std::uint8_t* const string_lengths, + char* const strings, + std::size_t& total_string_length, + std::size_t& valid_count) { + // clang-format off +#if ENCHANTUM_ENABLE_MSVC_SPEEDUP + constexpr auto skip_work_if_neg = IsBitFlag || std::is_unsigned_v || sizeof(IntType) <= 2 ? 0 : +// MSVC 19.31 and below don't cast int/unsigned int into `unsigned long long` (std::uint64_t) +// While higher versions do cast them +#if _MSC_VER <= 1931 + sizeof(IntType) == 4 +#else + std::is_same_v +#endif + ? sizeof(char32_t)*2-1 : sizeof(std::uint64_t)*2-1 - (sizeof(IntType)==8); // subtract 1 more from uint64_t since I am adding it in skip_if_cast_count +#endif + // clang-format on + for (std::size_t index = 0; index < array_size; ++index) { + #if _MSC_VER <= 1924 + // if it starts with the number 0 (because of 0x0) then it is a value + // and you cannot start an enum name with a digit so this is safe + if (*str == '0') { + #else + // if it starts with a '(' it is a cast! + if (*str == '(') { + #endif + #if ENCHANTUM_ENABLE_MSVC_SPEEDUP + if constexpr (skip_work_if_neg != 0) { + const auto i = min + static_cast(index); + str += least_length_when_casting + ((i < 0) * skip_work_if_neg); + } else { + str += least_length_when_casting; + } + #else + str += least_length_when_casting; + #endif + while (*str++ != ',') /*intentionally empty*/ + ; + } else { + str += least_length_when_value; + + // although gcc implementation of std::char_traits::find is using a for loop internally + // copying the code of the function makes it way slower to compile, this was surprising. + + if constexpr (IsBitFlag) + values[valid_count] = index == 0 ? IntType{} : static_cast(IntType{1} << (index - 1)); + else + values[valid_count] = static_cast(min + static_cast(index)); + + std::size_t i = 0; + while (str[i] != ',') strings[total_string_length++] = str[i++]; + string_lengths[valid_count++] = static_cast(i); + + total_string_length += null_terminated; + str += i + SZC(","); + } + } +} + +template +constexpr auto reflect(std::index_sequence) noexcept { + constexpr auto elements_local = []() { + constexpr auto ArraySize = sizeof...(Is) + is_bitflag; + using MinT = decltype(Min); + using Under = std::underlying_type_t; + using Underlying = std::make_unsigned_t, unsigned char, Under>>; + + constexpr auto str = [](const auto dependant) { + constexpr bool always_true = sizeof(dependant) != 0; + // dummy 0 + if constexpr (always_true && is_bitflag) // sizeof... to make contest dependant + return details::var_name(!always_true), static_cast(Underlying(1) << Is)..., 0>(); + else + return details::var_name(static_cast(Is) + Min)..., int(!always_true)>(); + }(0); + constexpr auto type_name_len = details::raw_type_name_func().size() - 1; + constexpr auto enum_in_array_len = details::enum_in_array_name_size(); + + ReflectStringReturnValue, ArraySize> ret; + details::parse_string>( + /*str = */ str, + #if _MSC_VER <= 1924 + /*least_length_when_casting=*/SZC("0x0"), + #else + /*least_length_when_casting=*/SZC("(enum ") + type_name_len + SZC(")0x0") + (sizeof(E) == 8), + #endif + /*least_length_when_value=*/details::prefix_length_or_zero + + (enum_in_array_len != 0 ? enum_in_array_len + SZC("::") : 0), + /*min = */ static_cast>(Min), + /*array_size = */ ArraySize, + /*null_terminated= */ NullTerminated, + /*enum_values= */ ret.values, + /*string_lengths= */ ret.string_lengths, + /*strings= */ ret.strings, + /*total_string_length*/ ret.total_string_length, + /*valid_count*/ ret.valid_count); + return ret; + }(); + + using Strings = std::array; + + struct { + decltype(elements_local) elements; + Strings strings{}; + } data = {elements_local}; + + const auto size = data.strings.size(); + auto* const data_string = data.strings.data(); + for (std::size_t i = 0; i < size; ++i) data_string[i] = elements_local.strings[i]; + return data; +} +} // namespace details +} // namespace enchantum + + #undef SZC +#else + #error unsupported compiler please open an issue for enchantum +#endif + +#include +#include + +#ifndef ENCHANTUM_CHECK_OUT_OF_BOUNDS_BY + #define ENCHANTUM_CHECK_OUT_OF_BOUNDS_BY 2 +#endif +#if ENCHANTUM_CHECK_OUT_OF_BOUNDS_BY < 0 + #error ENCHANTUM_CHECK_OUT_OF_BOUNDS_BY must not be a negative number. +#endif +namespace enchantum { + +#ifdef __cpp_lib_to_underlying +using ::std::to_underlying; +#else +template +[[nodiscard]] constexpr auto to_underlying(const E e) noexcept { + return static_cast>(e); +} +#endif + +namespace details { + +template +constexpr std::size_t get_index_sequence_max(const bool is_bitflag, + const bool has_fixed_underlying, + const std::size_t sizeof_enum, + const Int min, + const Int max, + const bool is_signed) { + (void)has_fixed_underlying; + if (!is_bitflag) return static_cast(max - min + 1); + +#if __clang_major__ >= 20 + if (!has_fixed_underlying) { + auto v = max; + std::size_t r = 1; + while (v >>= 1) r++; + return r; + } +#endif + return (sizeof_enum * CHAR_BIT) - is_signed; +} + +template +struct FinalReflectionResult { + std::array values{}; + // +1 for easier iteration on on last string + std::array string_indices{}; +}; + +template ::min, decltype(Min) Max = enum_traits::max> +inline constexpr auto reflection_data_impl = details::reflect( + std::make_index_sequence, + has_fixed_underlying_type, + sizeof(E), + Min, + Max, + std::is_signed_v>)>{}); + +// Thanks https://en.cppreference.com/w/cpp/utility/intcmp.html +template +constexpr bool cmp_less(const T t, const U u) noexcept { + if constexpr (std::is_signed_v == std::is_signed_v) + return t < u; + else if constexpr (std::is_signed_v) + return t < 0 || std::make_unsigned_t(t) < u; + else + return u >= 0 && t < std::make_unsigned_t(u); +} + +template +constexpr bool cmp_less(const bool t, const U u) noexcept { + return details::cmp_less(int(t), u); +} + +template +constexpr bool cmp_less(const T t, const bool u) noexcept { + return details::cmp_less(t, int(u)); +} + +constexpr bool cmp_less(const bool t, const bool u) noexcept { return int(t) < int(u); } + +template +constexpr T ClampToRange(U u) { + using L = std::numeric_limits; + if (details::cmp_less((L::max)(), u)) return (L::max)(); + if (details::cmp_less(u, (L::min)())) return (L::min)(); + return T(u); +} +template +constexpr auto get_reflection_data() noexcept { + constexpr auto elements = reflection_data_impl.elements; + using StringLengthType = + std::conditional_t<(elements.total_string_length < UINT8_MAX), std::uint8_t, std::uint16_t>; + +#if ENCHANTUM_CHECK_OUT_OF_BOUNDS_BY >= 2 + if constexpr ( + #if __clang_major__ >= 20 + has_fixed_underlying_type && + #endif + !details::has_specialized_traits) { + static_assert(elements.valid_count == reflection_data_impl>(enum_traits::min * ENCHANTUM_CHECK_OUT_OF_BOUNDS_BY), + details::ClampToRange>(enum_traits::max * ENCHANTUM_CHECK_OUT_OF_BOUNDS_BY) + >.elements.valid_count, + "enchantum has detected that this enum is not fully reflected. Please look at https://github.com/ZXShady/enchantum/blob/main/docs/features.md#enchantum_check_out_of_bounds_by for more information"); + } +#endif + FinalReflectionResult ret; + std::size_t i = 0; + StringLengthType string_index = 0; + for (; i < elements.valid_count; ++i) { + ret.values[i] = static_cast(elements.values[i]); + // "aabc" + + ret.string_indices[i] = string_index; +#if defined(ENCAHNTUM_DETAILS_GCC_MAJOR) && ENCAHNTUM_DETAILS_GCC_MAJOR <= 10 + // false positives from T += T + // it does not make sense. + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wconversion" +#endif + string_index += static_cast(elements.string_lengths[i] + NullTerminated); +#if defined(ENCAHNTUM_DETAILS_GCC_MAJOR) && ENCAHNTUM_DETAILS_GCC_MAJOR <= 10 + #pragma GCC diagnostic pop +#endif + } + ret.string_indices[i] = string_index; + return ret; +} + +template +inline constexpr auto reflection_data_string_storage = details::reflection_data_impl.strings; + +template +inline constexpr auto reflection_data = details::get_reflection_data(); + +template +inline constexpr auto reflection_string_indices = reflection_data.string_indices; +} // namespace details + +#ifdef __cpp_concepts +template , bool NullTerminated = true> +#else +template , + bool NullTerminated = true, + std::enable_if_t, int> = 0> +#endif +inline constexpr auto entries = []() { + +#if defined(__NVCOMPILER) + // nvc++ had issues with that and did not allow it. it just did not work after testing in godbolt and I don't know + // why + const auto reflected = details::reflection_data; + const auto strings = details::reflection_data_string_storage.data(); +#else + const auto reflected = details::reflection_data, NullTerminated>; + const auto strings = details::reflection_data_string_storage, NullTerminated>.data(); +#endif + using Pairs = std::array; + Pairs ret{}; + constexpr auto size = ret.size(); + static_assert( + size != 0, + "enchantum failed to reflect this enum.\n" + "Please read https://github.com/ZXShady/enchantum/blob/main/docs/limitations.md before opening an " + "issue\n" + "with your enum type with all its namespace/classes it is defined inside to help the creator debug the " + "issues."); + auto* const ret_data = ret.data(); + + for (std::size_t i = 0; i < size; ++i) { + auto& [e, s] = ret_data[i]; + e = reflected.values[i]; + using StringView = std::remove_cv_t>; + s = StringView(strings + reflected.string_indices[i], + reflected.string_indices[i + 1] - reflected.string_indices[i] - NullTerminated); + } + return ret; +}(); + +namespace details { +template +constexpr auto get_values() noexcept { + constexpr auto enums = entries; + std::array ret{}; + const auto* const enums_data = enums.data(); + for (std::size_t i = 0; i < ret.size(); ++i) ret[i] = enums_data[i].first; + return ret; +} + +template +constexpr auto get_names() noexcept { + constexpr auto enums = entries, NullTerminated>; + std::array ret{}; + const auto* const enums_data = enums.data(); + for (std::size_t i = 0; i < ret.size(); ++i) ret[i] = enums_data[i].second; + return ret; +} + +} // namespace details + +template +inline constexpr auto values = details::get_values(); + +#ifdef __cpp_concepts +template +#else +template , int> = 0> +#endif +inline constexpr auto names = details::get_names(); + +template +inline constexpr auto min = entries.front().first; + +template +inline constexpr auto max = entries.back().first; + +template +inline constexpr std::size_t count = entries.size(); + +template +inline constexpr bool has_zero_flag = [](const auto is_bitflag) { + if constexpr (is_bitflag.value) { + for (const auto v : values) + if (static_cast>(v) == 0) return true; + } + return false; +}(std::bool_constant>{}); + +template +inline constexpr bool is_contiguous = + static_cast(enchantum::to_underlying(max) - enchantum::to_underlying(min)) + 1 == count; + +template +inline constexpr bool is_contiguous_bitflag = [](const auto is_bitflag) { + if constexpr (is_bitflag.value) { + constexpr auto& enums = entries; + using T = std::underlying_type_t; + for (auto i = std::size_t{has_zero_flag}; i < enums.size() - 1; ++i) + if (T(enums[i].first) << 1 != T(enums[i + 1].first)) return false; + return true; + } else { + return false; } +}(std::bool_constant>{}); + +#ifdef __cpp_concepts +template +concept ContiguousEnum = Enum && is_contiguous; +template +concept ContiguousBitFlagEnum = BitFlagEnum && is_contiguous_bitflag; +#endif + +} // namespace enchantum + +#ifdef __cpp_impl_three_way_comparison + #include +#endif + +#if (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) && __has_include() + #include +namespace enchantum { +namespace details { +using ::std::countr_zero; } +} // namespace enchantum +#else +namespace enchantum { +namespace details { +template +constexpr int countr_zero(T x) { + if (x == 0) return sizeof(T) * 8; -template -constexpr auto var_name() noexcept { - // constexpr auto f() [with auto _ = std::array{std::__array_traits::_Type{a, b, c, e, d, (E)6}}] - constexpr std::size_t funcsig_off = SZC("constexpr auto enchantum::details::var_name() [with auto ...Vs = {"); - return std::string_view(__PRETTY_FUNCTION__ + funcsig_off, SZC(__PRETTY_FUNCTION__) - funcsig_off - SZC("}]")); + int count = 0; + while ((x & 1) == 0) { + x = static_cast(x >> 1); + ++count; + } + return count; } +} // namespace details +} // namespace enchantum +#endif +#include +#include +#include +#if defined(ENCAHNTUM_DETAILS_GCC_MAJOR) && ENCAHNTUM_DETAILS_GCC_MAJOR <= 10 + // false positives from T += T + // it does not make sense. + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wconversion" +#endif -template -inline constexpr auto static_storage_for = Copy; +namespace enchantum { +namespace details { -template -constexpr auto reflect() noexcept { - constexpr auto Min = enum_traits::min; - constexpr auto Max = enum_traits::max; +struct senitiel {}; - constexpr auto elements = []() { - constexpr auto length_of_enum_in_template_array_casting = - details::length_of_enum_in_template_array_if_casting(); - constexpr auto ArraySize = - 1 + std::size_t{is_bitflag ? (sizeof(E) * CHAR_BIT - std::is_signed_v) : Max - Min}; - // constexpr auto Array = details::generate_arrays(); - using Under = std::underlying_type_t; - using Underlying = std::make_unsigned_t, unsigned char, Under>>; +template +struct sized_iterator { + static_assert(Size < INT16_MAX, "Too many enum entries"); - constexpr auto ConstStr = [](std::index_sequence) { - if constexpr (sizeof...(Idx) && is_bitflag) // sizeof... to make contest dependant - return details::var_name(Underlying(1) << Idx))...>(); - else - return details::var_name<__builtin_bit_cast( - E, static_cast(static_cast(Idx) + Min))...>(); - }(std::make_index_sequence>()); - auto str = ConstStr; - struct RetVal { - struct ElementPair { - E value; - std::size_t length; - }; +private: + using IndexType = std::conditional_t<(Size <= INT8_MAX), std::int8_t, std::int16_t>; - ElementPair pairs[ArraySize]; - char strings[ConstStr.size()]{}; - std::size_t total_string_length = 0; - std::size_t valid_count = 0; - } ret; - constexpr auto enum_in_array_len = details::enum_in_array_name_size(); - for (std::size_t index = 0; index < ArraySize; ++index) { - if (str.front() == '(') { - str.remove_prefix(SZC("(") + length_of_enum_in_template_array_casting + - SZC(")0")); // there is atleast 1 base 10 digit - // if(!str.empty()) - // std::cout << "after str \"" << str << '"' << '\n'; +public: + IndexType index{}; + constexpr CRTP& operator+=(const std::ptrdiff_t offset) & noexcept { + index += static_cast(offset); + return static_cast(*this); + } + constexpr CRTP& operator-=(const std::ptrdiff_t offset) & noexcept { + index -= static_cast(offset); + return static_cast(*this); + } - if (const auto commapos = str.find(','); commapos != str.npos) str.remove_prefix(commapos + 2); + constexpr CRTP& operator++() & noexcept { + ++index; + return static_cast(*this); + } + constexpr CRTP& operator--() & noexcept { + --index; + return static_cast(*this); + } - // std::cout << "strsize \"" << str.size() << '"' << '\n'; - } else { - if constexpr (enum_in_array_len != 0) str.remove_prefix(enum_in_array_len + SZC("::")); - if constexpr (details::prefix_length_or_zero != 0) { - str.remove_prefix(details::prefix_length_or_zero); - } - const auto commapos = str.find(','); - - { - const auto name = str.substr(0, commapos); - const auto name_size = name.size(); - const auto* const name_data = name.data(); - - if constexpr (is_bitflag) - ret.pairs[ret.valid_count++] = {index == 0 ? E() : E(Underlying{1} << (index - 1)), name_size}; - else - ret.pairs[ret.valid_count++] = {E(Min + static_cast(index)), name_size}; - - for (std::size_t i = 0; i < name_size; ++i) ret.strings[ret.total_string_length++] = name_data[i]; - ret.total_string_length += ShouldNullTerminate; - - if (commapos != str.npos) str.remove_prefix(commapos + 2); - } - } - } - return ret; - }(); + [[nodiscard]] constexpr CRTP operator++(int) & noexcept { + auto copy = static_cast(*this); + ++*this; + return copy; + } + [[nodiscard]] constexpr CRTP operator--(int) & noexcept { + auto copy = static_cast(*this); + --*this; + return copy; + } - constexpr auto strings = [](const auto total_length, const char* data) { - std::array ret; - auto* const ret_data = ret.data(); - for (std::size_t i = 0; i < total_length.value; ++i) ret_data[i] = data[i]; - return ret; - }(std::integral_constant{}, elements.strings); + [[nodiscard]] constexpr friend CRTP operator+(CRTP it, const std::ptrdiff_t offset) noexcept { + it += offset; + return it; + } - std::array ret; - constexpr const auto* str = static_storage_for.data(); - for (std::size_t i = 0, string_index = 0; i < elements.valid_count; ++i) { - const auto& [e, length] = elements.pairs[i]; - auto& [re, rs] = ret[i]; - re = e; + [[nodiscard]] constexpr friend CRTP operator+(const std::ptrdiff_t offset, CRTP it) noexcept { + it += offset; + return it; + } - rs = {str + string_index, str + string_index + length}; - string_index += length + ShouldNullTerminate; + [[nodiscard]] constexpr friend CRTP operator-(CRTP it, const std::ptrdiff_t offset) noexcept { + it -= offset; + return it; } - return ret; -} -} // namespace details + [[nodiscard]] constexpr std::ptrdiff_t operator-(const sized_iterator that) const noexcept { + return index - that.index; + } -} // namespace enchantum + [[nodiscard]] constexpr std::ptrdiff_t operator-(senitiel) const noexcept { return index - Size; } + [[nodiscard]] friend constexpr std::ptrdiff_t operator-(senitiel, sized_iterator it) noexcept { + return Size - it.index; + } - #undef SZC -#elif defined(_MSC_VER) + [[nodiscard]] constexpr bool operator==(const sized_iterator that) const noexcept { return that.index == index; }; + [[nodiscard]] constexpr bool operator==(senitiel) const noexcept { return Size == index; } - #include - #include - #include - #include - #include - #include +#ifdef __cpp_impl_three_way_comparison + [[nodiscard]] constexpr auto operator<=>(const sized_iterator that) const noexcept { return index <=> that.index; }; + [[nodiscard]] constexpr auto operator<=>(senitiel) const noexcept { return index <=> Size; } +#else - // This macro controls the compile time optimization of msvc - // This macro may break some enums with very large enum ranges selected. - // **may** as in I have not found a case where it does - // but it speeds up compilation massivly. - // from 20 secs to 14.6 secs - // from 119 secs to 85 - #ifndef ENCHANTUM_ENABLE_MSVC_SPEEDUP - #define ENCHANTUM_ENABLE_MSVC_SPEEDUP 1 - #endif -namespace enchantum { + [[nodiscard]] constexpr bool operator!=(const sized_iterator that) const noexcept { return that.index != index; }; + [[nodiscard]] constexpr bool operator!=(senitiel) const noexcept { return Size != index; } -struct simple_string_view { - const char* begin; - const char* end; - constexpr std::size_t find(const char c) const noexcept { - for (auto copy = begin; copy != end; ++copy) - if (*copy == c) return std::size_t(copy - begin); + [[nodiscard]] friend constexpr bool operator==(senitiel, const sized_iterator it) noexcept { + return Size == it.index; } - constexpr std::size_t find_comma() const noexcept { - for (auto copy = begin; copy != end; ++copy) - if (*copy == ',') return std::size_t(copy - begin); - } -}; - - #define SZC(x) (sizeof(x) - 1) -namespace details { -template -constexpr auto enum_in_array_name_size() noexcept { - string_view s = __FUNCSIG__ + SZC("auto __cdecl enchantum::details::enum_in_array_name_size<"); - s.remove_suffix(SZC(">(void) noexcept")); - if constexpr (ScopedEnum) { - if (s.front() == '(') { - s.remove_prefix(SZC("(enum ")); - s.remove_suffix(SZC(")0x0")); - return s.size(); - } - return s.substr(0, s.rfind(':') - 1).size(); - } else { - if (s.front() == '(') { - s.remove_prefix(SZC("(enum ")); - s.remove_suffix(SZC(")0x0")); - } - if (const auto pos = s.rfind(':'); pos != s.npos) return pos - 1; - return std::size_t(0); + [[nodiscard]] friend constexpr bool operator!=(senitiel, const sized_iterator it) noexcept { + return Size != it.index; } -} -template -constexpr auto var_name() noexcept { - // auto __cdecl f{enum - // `anonymous-namespace'::UnscopedAnon + [[nodiscard]] constexpr bool operator<(const sized_iterator that) const noexcept { return index < that.index; }; + [[nodiscard]] constexpr bool operator>(const sized_iterator that) const noexcept { return index > that.index; }; + [[nodiscard]] constexpr bool operator<=(const sized_iterator that) const noexcept { return index <= that.index; }; + [[nodiscard]] constexpr bool operator>=(const sized_iterator that) const noexcept { return index >= that.index; }; + + [[nodiscard]] constexpr bool operator<(senitiel) const noexcept { return index < Size; }; + [[nodiscard]] constexpr bool operator>(senitiel) const noexcept { return index > Size; }; + [[nodiscard]] constexpr bool operator<=(senitiel) const noexcept { return index <= Size; }; + [[nodiscard]] constexpr bool operator>=(senitiel) const noexcept { return index >= Size; }; + + [[nodiscard]] friend constexpr bool operator<(senitiel, const sized_iterator it) noexcept { + return Size < it.index; + }; + [[nodiscard]] friend constexpr bool operator>(senitiel, const sized_iterator it) noexcept { + return Size > it.index; + }; + [[nodiscard]] friend constexpr bool operator<=(senitiel, const sized_iterator it) noexcept { + return Size <= it.index; + }; + [[nodiscard]] friend constexpr bool operator>=(senitiel, const sized_iterator it) noexcept { + return Size >= it.index; + }; - using T = typename decltype(Array)::value_type; - std::size_t funcsig_off = SZC("auto __cdecl enchantum::details::var_name.size(); - funcsig_off += type_name_len + SZC(","); - constexpr auto Size = Array.size(); - // clang-format off - funcsig_off += Size < 10 ? 1 - : Size < 100 ? 2 - : Size < 1000 ? 3 - : Size < 10000 ? 4 - : Size < 100000 ? 5 - : Size < 1000000 ? 6 - : Size < 10000000 ? 7 - : Size < 100000000 ? 8 - : Size < 1000000000 ? 9 - : 10; - // clang-format on - funcsig_off += SZC(">{enum ") + type_name_len; - return string_view(__FUNCSIG__ + funcsig_off, SZC(__FUNCSIG__) - funcsig_off - SZC("}>(void) noexcept")); -} - -template -inline constexpr auto static_storage_for = Copy; - -template -constexpr auto get_elements() { - constexpr auto Min = enum_traits::min; - constexpr auto Max = enum_traits::max; - - constexpr auto Array = details::generate_arrays(); - const E* const ArrayData = Array.data(); - constexpr auto ConstStr = var_name(); - constexpr auto StringSize = ConstStr.size(); - constexpr auto ArraySize = Array.size() - 1; - auto str = simple_string_view(ConstStr.data(), ConstStr.data() + ConstStr.size()); - constexpr auto type_name_len = raw_type_name.size(); - constexpr auto enum_in_array_len = details::enum_in_array_name_size(); - - struct RetVal { - struct ElementPair { - E value; - std::size_t string_length; - }; - ElementPair pairs[ArraySize]{}; - char strings[StringSize - (details::Min(type_name_len, enum_in_array_len) * ArraySize)]{}; - std::size_t total_string_length = 0; - std::size_t valid_count = 0; - } ret; - - // there is atleast 1 base 16 hex digit - // MSVC adds an extra 0 prefix at front if the underlying type equals to 8 bytes. - // Don't ask why - constexpr auto skip_if_cast_count = SZC("(enum ") + type_name_len + SZC(")0x0") + (sizeof(E) == 8); - // clang-format off -#if ENCHANTUM_ENABLE_MSVC_SPEEDUP - using Underlying = std::underlying_type_t; - constexpr auto skip_work_if_neg = std::is_unsigned_v || sizeof(E) <= 2 ? 0 : -// MSVC 19.31 and below don't cast int/unsigned int into `unsigned long long` (std::uint64_t) -// While higher versions do cast them -#if _MSC_VER <= 1931 - sizeof(Underlying) == 4 -#else - std::is_same_v #endif - ? sizeof(char32_t)*2-1 : sizeof(std::uint64_t)*2-1 - (sizeof(E)==8); // subtract 1 more from uint64_t since I am adding it in skip_if_cast_count -#endif - // clang-format on +}; - for (std::size_t index = 0; index < ArraySize; ++index) { - if (*str.begin == '(') { - #if ENCHANTUM_ENABLE_MSVC_SPEEDUP - if constexpr (skip_work_if_neg != 0) { - const auto i = static_cast>(ArrayData[index]); - str.begin += skip_if_cast_count + ((i < 0) * skip_work_if_neg); - } else { - str.begin += skip_if_cast_count; - } - #else - str.begin += skip_if_cast_count; - #endif +template +struct names_generator_t { + [[nodiscard]] static constexpr std::size_t size() noexcept { return count; } - str.begin += str.find_comma() + 1; - } else { - if constexpr (enum_in_array_len != 0) str.begin += enum_in_array_len + SZC("::"); + struct iterator : sized_iterator(size())> { + using value_type = String; + [[nodiscard]] constexpr String operator*() const noexcept { + const auto* const p = details::reflection_string_indices.data(); + const auto* const strings = details::reflection_data_string_storage.data(); + return String(strings + p[this->index], p[this->index + 1] - p[this->index] - NullTerminated); + } - if constexpr (details::prefix_length_or_zero != 0) str.begin += details::prefix_length_or_zero; + [[nodiscard]] constexpr String operator[](const std::ptrdiff_t i) const noexcept { return *(*this + i); } + }; - const auto commapos = str.find_comma(); + [[nodiscard]] static constexpr auto begin() { return iterator{}; } + [[nodiscard]] static constexpr auto end() { return senitiel{}; } - ret.pairs[ret.valid_count++] = {ArrayData[index], commapos}; - for (std::size_t i = 0; i < commapos; ++i) ret.strings[ret.total_string_length++] = str.begin[i]; - ret.total_string_length += ShouldNullTerminate; + [[nodiscard]] constexpr auto operator[](const std::size_t i) const noexcept { + return *(begin() + static_cast(i)); + } +}; - str.begin += commapos + 1; +template +struct values_generator_t { + [[nodiscard]] static constexpr std::size_t size() noexcept { return count; } + + struct iterator : sized_iterator(size())> { + using value_type = E; + [[nodiscard]] constexpr E operator*() const noexcept { + using T = std::underlying_type_t; + + if constexpr (is_contiguous) { + return static_cast(static_cast(min) + static_cast(this->index)); + } else if constexpr (is_contiguous_bitflag) { + using UT = std::make_unsigned_t; + constexpr auto real_min_offset = details::countr_zero(static_cast(values[has_zero_flag])); + + if constexpr (has_zero_flag) + if (this->index == 0) return E{}; + return static_cast(UT{1} << (real_min_offset + static_cast(this->index - has_zero_flag))); + } else { + return values[static_cast(this->index)]; + } } + [[nodiscard]] constexpr E operator[](const std::ptrdiff_t i) const noexcept { return *(*this + i); } + }; + + [[nodiscard]] static constexpr auto begin() { return iterator{}; } + [[nodiscard]] static constexpr auto end() { return senitiel{}; } + + [[nodiscard]] constexpr auto operator[](const std::size_t i) const noexcept { + return *(begin() + static_cast(i)); } - return ret; -} +}; -template -constexpr auto reflect() noexcept { - constexpr auto elements = details::get_elements(); +template , bool NullTerminated = true> +struct entries_generator_t { + [[nodiscard]] static constexpr std::size_t size() noexcept { return count; } - constexpr auto strings = [](const auto total_length, const char* const name_data) { - std::array ret; - auto* const ret_data = ret.data(); - for (std::size_t i = 0; i < total_length.value; ++i) ret_data[i] = name_data[i]; - return ret; - }(std::integral_constant{}, elements.strings); + struct iterator : sized_iterator(size())> { + using value_type = Pair; + [[nodiscard]] constexpr Pair operator*() const noexcept { + return Pair{ + values_generator_t{}[static_cast(this->index)], + names_generator_t{}[static_cast(this->index)], + }; + } + [[nodiscard]] constexpr Pair operator[](const std::ptrdiff_t i) const noexcept { return *(*this + i); } + }; - std::array ret; - auto* const ret_data = ret.data(); - constexpr const auto* str = static_storage_for.data(); - for (std::size_t i = 0, string_index = 0; i < elements.valid_count; ++i) { - const auto& [e, length] = elements.pairs[i]; - auto& [re, rs] = ret_data[i]; - re = e; + [[nodiscard]] static constexpr auto begin() { return iterator{}; } + [[nodiscard]] static constexpr auto end() { return senitiel{}; } - rs = {str + string_index, str + string_index + length}; - string_index += length + ShouldNullTerminate; + [[nodiscard]] constexpr auto operator[](const std::size_t i) const noexcept { + return *(begin() + static_cast(i)); } +}; - return ret; -} } // namespace details -} // namespace enchantum - - #undef SZC -#endif +template +inline constexpr details::values_generator_t values_generator{}; -#include -#include +#ifdef __cpp_concepts +template +inline constexpr details::names_generator_t names_generator{}; -namespace enchantum { +template , bool NullTerminated = true> +inline constexpr details::entries_generator_t entries_generator{}; -#ifdef __cpp_lib_to_underlying -using ::std::to_underlying; #else -template -[[nodiscard]] constexpr auto to_underlying(const E e) noexcept { - return static_cast>(e); -} -#endif - -template , bool ShouldNullTerminate = true> -inline constexpr auto entries = details::reflect, Pair, ShouldNullTerminate>(); - -template -inline constexpr auto values = []() { - constexpr auto& enums = entries; - std::array ret; - for (std::size_t i = 0; i < ret.size(); ++i) ret[i] = enums[i].first; - return ret; -}(); - -template -inline constexpr auto names = []() { - constexpr auto& enums = entries, NullTerminated>; - std::array ret; - for (std::size_t i = 0; i < ret.size(); ++i) ret[i] = enums[i].second; - return ret; -}(); +template , int> = 0> +inline constexpr details::names_generator_t names_generator{}; + +template , + bool NullTerminated = true, + std::enable_if_t, int> = 0> +inline constexpr details::entries_generator_t entries_generator{}; -template -inline constexpr auto min = entries.front().first; +#endif -template -inline constexpr auto max = entries.back().first; +} // namespace enchantum -template -inline constexpr std::size_t count = entries.size(); +#if defined(ENCAHNTUM_DETAILS_GCC_MAJOR) && ENCAHNTUM_DETAILS_GCC_MAJOR <= 10 + #pragma GCC diagnostic pop +#endif -} // namespace enchantum -#include #include #include +#if defined(ENCAHNTUM_DETAILS_GCC_MAJOR) && ENCAHNTUM_DETAILS_GCC_MAJOR <= 10 + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wconversion" + #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + namespace enchantum { namespace details { +template +constexpr bool call_predicate(const BinaryPredicate binary_pred, const string_view a, const string_view b) { + if constexpr (std::is_invocable_v) { + const auto a_size = a.size(); + if (a_size != b.size()) return false; + const auto a_data = a.data(); + const auto b_data = b.data(); + + for (std::size_t i = 0; i < a_size; ++i) + if (!binary_pred(a_data[i], b_data[i])) return false; + return true; + } else { + static_assert(std::is_invocable_v, + "BinaryPredicate must be callable with atleast 2 char or 2 string_views"); + return binary_pred(a, b); + } +} + constexpr std::pair minmax_string_size(const string_view* begin, const string_view* const end) { using T = std::size_t; @@ -953,95 +1587,61 @@ constexpr std::pair minmax_string_size(const string_vi } // namespace details -template -inline constexpr bool has_zero_flag = false; - -template -inline constexpr bool has_zero_flag = []() { - for (const auto v : values) - if (static_cast>(v) == 0) return true; - return false; -}(); - -template -inline constexpr bool is_contiguous = false; - -template -inline constexpr bool is_contiguous = - static_cast(to_underlying(max) - to_underlying(min)) + 1 == count; - -template -concept ContiguousEnum = Enum && is_contiguous; - -template -inline constexpr bool is_contiguous_bitflag = false; - -template -inline constexpr bool is_contiguous_bitflag = []() { - constexpr auto& enums = entries; - using T = std::underlying_type_t; - for (auto i = std::size_t{has_zero_flag}; i < enums.size() - 1; ++i) - if (T(enums[i].first) << 1 != T(enums[i + 1].first)) return false; - return true; -}(); - -template -concept ContiguousBitFlagEnum = BitFlagEnum && is_contiguous_bitflag; - -template +template [[nodiscard]] constexpr bool contains(const std::underlying_type_t value) noexcept { using T = std::underlying_type_t; if (value < T(min) || value > T(max)) return false; - if constexpr (is_bitflag) { + if constexpr (is_contiguous_bitflag) { if constexpr (has_zero_flag) if (value == 0) return true; + const auto u = static_cast>(value); - return std::popcount(static_cast>(value)) == 1; + // std::has_single_bit + return u != 0 && (u & (u - 1)) == 0; } else if constexpr (is_contiguous) { return true; } else { - for (const auto v : values) + for (const auto v : values_generator) if (static_cast(v) == value) return true; return false; } } -template +template [[nodiscard]] constexpr bool contains(const E value) noexcept { return enchantum::contains(static_cast>(value)); } -template +template [[nodiscard]] constexpr bool contains(const string_view name) noexcept { constexpr auto minmax = details::minmax_string_size(names.data(), names.data() + names.size()); if (const auto size = name.size(); size < minmax.first || size > minmax.second) return false; - for (const auto& s : names) + for (const auto s : names_generator) if (s == name) return true; return false; } -template BinaryPredicate> -[[nodiscard]] constexpr bool contains(const string_view name, const BinaryPredicate binary_predicate) noexcept { - for (const auto& s : names) - if (binary_predicate(name, s)) return true; +template +[[nodiscard]] constexpr bool contains(const string_view name, const BinaryPred binary_pred) noexcept { + for (const auto s : names_generator) + if (details::call_predicate(binary_pred, name, s)) return true; return false; } namespace details { -template +template struct index_to_enum_functor { [[nodiscard]] constexpr optional operator()(const std::size_t index) const noexcept { - optional ret; - if (index < values.size()) ret.emplace(values[index]); - return ret; + if (index < count) return optional(values_generator[index]); + return optional(); } }; struct enum_to_index_functor { - template + template [[nodiscard]] constexpr optional operator()(const E e) const noexcept { using T = std::underlying_type_t; @@ -1056,71 +1656,65 @@ struct enum_to_index_functor { if (static_cast(e) == 0) return optional(0); // assumes 0 is the index of value `0` using U = std::make_unsigned_t; - return has_zero + std::countr_zero(static_cast(e)) - - std::countr_zero(static_cast(values[has_zero])); + return has_zero + details::countr_zero(static_cast(e)) - + details::countr_zero(static_cast(values_generator[has_zero])); } } else { - for (std::size_t i = 0; i < values.size(); ++i) { - if (values[i] == e) return i; + for (std::size_t i = 0; i < count; ++i) { + if (values_generator[i] == e) return optional(i); } } return optional(); } }; -template +template struct cast_functor { [[nodiscard]] constexpr optional operator()(const std::underlying_type_t value) const noexcept { - optional a; // rvo not that it really matters - if (!enchantum::contains(value)) return a; - a.emplace(static_cast(value)); - return a; + if (!enchantum::contains(value)) return optional(); + return optional(static_cast(value)); } [[nodiscard]] constexpr optional operator()(const string_view name) const noexcept { - optional a; // rvo not that it really matters - constexpr auto minmax = details::minmax_string_size(names.data(), names.data() + names.size()); - if (const auto size = name.size(); size < minmax.first || size > minmax.second) return a; // nullopt + if (const auto size = name.size(); size < minmax.first || size > minmax.second) + return optional(); // nullopt for (std::size_t i = 0; i < count; ++i) { - if (names[i] == name) { - a.emplace(values[i]); - return a; + if (names_generator[i] == name) { + return optional(values_generator[i]); } } - return a; // nullopt + return optional(); // nullopt } - template BinaryPred> + template [[nodiscard]] constexpr optional operator()(const string_view name, - const BinaryPred binary_predicate) const noexcept { - optional a; // rvo not that it really matters + const BinaryPred binary_pred) const noexcept { for (std::size_t i = 0; i < count; ++i) { - if (binary_predicate(name, names[i])) { - a.emplace(values[i]); - return a; + if (details::call_predicate(binary_pred, name, names_generator[i])) { + return optional(values_generator[i]); } } - return a; + return optional(); } }; } // namespace details -template +template inline constexpr details::index_to_enum_functor index_to_enum{}; inline constexpr details::enum_to_index_functor enum_to_index{}; -template +template inline constexpr details::cast_functor cast{}; namespace details { struct to_string_functor { - template + template [[nodiscard]] constexpr string_view operator()(const E value) const noexcept { - if (const auto i = enchantum::enum_to_index(value)) return names[*i]; + if (const auto i = enchantum::enum_to_index(value)) return names_generator[*i]; return string_view(); } }; @@ -1130,39 +1724,41 @@ inline constexpr details::to_string_functor to_string{}; } // namespace enchantum +#if defined(ENCAHNTUM_DETAILS_GCC_MAJOR) && ENCAHNTUM_DETAILS_GCC_MAJOR <= 10 + #pragma GCC diagnostic pop +#endif + +#if defined(ENCAHNTUM_DETAILS_GCC_MAJOR) && ENCAHNTUM_DETAILS_GCC_MAJOR <= 10 + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wconversion" + #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + namespace enchantum { -template +template inline constexpr E value_ors = [] { + static_assert(is_bitflag, ""); using T = std::underlying_type_t; T ret{}; - for (const auto val : values) ret |= static_cast(val); + for (const auto val : values_generator) ret |= static_cast(val); return static_cast(ret); }(); -template +template [[nodiscard]] constexpr bool contains_bitflag(const std::underlying_type_t value) noexcept { - using T = std::underlying_type_t; - if constexpr (is_contiguous_bitflag) { - return value >= static_cast(min) && value <= static_cast(value_ors); - } else { - if (value == 0) return has_zero_flag; - T valid_bits = 0; + if constexpr (!has_zero_flag) + if (value == 0) return false; - for (auto i = std::size_t{has_zero_flag}; i < count; ++i) { - const auto v = static_cast(values[i]); - if ((value & v) == v) valid_bits |= v; - } - return valid_bits == value; - } + return value == (static_cast>(value_ors) & value); } -template +template [[nodiscard]] constexpr bool contains_bitflag(const E value) noexcept { return enchantum::contains_bitflag(static_cast>(value)); } -template BinaryPred> +template [[nodiscard]] constexpr bool contains_bitflag(const string_view s, const char sep, const BinaryPred binary_pred) noexcept { @@ -1174,7 +1770,7 @@ template BinaryPred> return enchantum::contains(s.substr(pos), binary_pred); } -template +template [[nodiscard]] constexpr bool contains_bitflag(const string_view s, const char sep = '|') noexcept { std::size_t pos = 0; for (std::size_t i = s.find(sep); i != s.npos; i = s.find(sep, pos)) { @@ -1184,28 +1780,28 @@ template return enchantum::contains(s.substr(pos)); } -template +template [[nodiscard]] constexpr String to_string_bitflag(const E value, const char sep = '|') { using T = std::underlying_type_t; if constexpr (has_zero_flag) - if (static_cast(value) == 0) return String(names[0]); + if (static_cast(value) == 0) return String(names_generator[0]); String name; T check_value = 0; for (auto i = std::size_t{has_zero_flag}; i < count; ++i) { - const auto& [v, s] = entries[i]; - const auto casted_v = static_cast(v); - if (casted_v == (static_cast(value) & casted_v)) { + const auto v = static_cast(values_generator[i]); + if (v == (static_cast(value) & v)) { + const auto s = names_generator[i]; if (!name.empty()) name.append(1, sep); // append separator if not the first value name.append(s.data(), s.size()); // not using operator += since this may not be std::string_view always - check_value |= casted_v; + check_value |= v; } } if (check_value == static_cast(value)) return name; return String(); } -template BinaryPred> +template [[nodiscard]] constexpr optional cast_bitflag(const string_view s, const char sep, const BinaryPred binary_pred) noexcept { @@ -1225,23 +1821,27 @@ template BinaryPred> return optional(); } -template +template [[nodiscard]] constexpr optional cast_bitflag(const string_view s, const char sep = '|') noexcept { return enchantum::cast_bitflag(s, sep, [](const auto& a, const auto& b) { return a == b; }); } -template +template [[nodiscard]] constexpr optional cast_bitflag(const std::underlying_type_t value) noexcept { return enchantum::contains_bitflag(value) ? optional(static_cast(value)) : optional(); } } // namespace enchantum +#if defined(ENCAHNTUM_DETAILS_GCC_MAJOR) && ENCAHNTUM_DETAILS_GCC_MAJOR <= 10 + #pragma GCC diagnostic pop +#endif + #include namespace enchantum { namespace details { -template +template std::string format(E e) noexcept { if constexpr (is_bitflag) { if (const auto name = enchantum::to_string_bitflag(e); !name.empty()) { @@ -1260,7 +1860,6 @@ std::string format(E e) noexcept { } // namespace details } // namespace enchantum -#include #include namespace enchantum { @@ -1333,7 +1932,7 @@ constexpr auto for_each(Func& f, std::index_sequence) { } // namespace details -template +template constexpr void for_each(Func f) // intentional not const { details::for_each(f, std::make_index_sequence>{}); @@ -1345,27 +1944,18 @@ constexpr void for_each(Func f) // intentional not const namespace enchantum { -template -class array : public std::array> { -private: - using base = std::array>; +template >> +class array : public Container { + static_assert(std::is_enum_v); public: + using container_type = Container; using index_type = E; - using typename base::const_iterator; - using typename base::const_pointer; - using typename base::const_reference; - using typename base::const_reverse_iterator; - using typename base::difference_type; - using typename base::iterator; - using typename base::pointer; - using typename base::reference; - using typename base::reverse_iterator; - using typename base::size_type; - using typename base::value_type; - - using base::at; - using base::operator[]; + using typename Container::const_reference; + using typename Container::reference; + + using Container::at; + using Container::operator[]; [[nodiscard]] constexpr reference at(const E index) { if (const auto i = enchantum::enum_to_index(index)) return operator[](*i); @@ -1404,28 +1994,28 @@ ENCHANTUM_ALIAS_BITSET; #endif } // namespace details -template -class bitset : public details::bitset> { -private: - using base = details::bitset>; +template >> +class bitset : public Container { + static_assert(std::is_enum_v); public: - using typename base::reference; + using container_type = Container; + using typename Container::reference; - using base::operator[]; - using base::flip; - using base::reset; - using base::set; - using base::test; + using Container::operator[]; + using Container::flip; + using Container::reset; + using Container::set; + using Container::test; - using base::base; - using base::operator=; + using Container::Container; + using Container::operator=; [[nodiscard]] string to_string(const char sep = '|') const { string name; for (std::size_t i = 0; i < enchantum::count; ++i) { if (test(i)) { - const auto s = enchantum::names[i]; + const auto s = enchantum::names_generator[i]; if (!name.empty()) name += sep; name.append(s.data(), s.size()); // not using operator += since this may not be std::string_view always } @@ -1433,7 +2023,9 @@ class bitset : public details::bitset> { return name; } - [[nodiscard]] constexpr auto to_string(const char zero, const char one) const { return base::to_string(zero, one); } + [[nodiscard]] constexpr auto to_string(const char zero, const char one) const { + return Container::to_string(zero, one); + } constexpr bitset(const std::initializer_list values) noexcept { for (auto value : values) { @@ -1492,49 +2084,52 @@ enchantum::contains(Flags::F1); // considered `BitFlagEnum` concept woops! ODR! */ -namespace enchantum::bitwise_operators { -template +namespace enchantum { +namespace bitwise_operators { + +template [[nodiscard]] constexpr E operator~(E e) noexcept { return static_cast(~static_cast>(e)); } -template +template [[nodiscard]] constexpr E operator|(E a, E b) noexcept { using T = std::underlying_type_t; return static_cast(static_cast(a) | static_cast(b)); } -template +template [[nodiscard]] constexpr E operator&(E a, E b) noexcept { using T = std::underlying_type_t; return static_cast(static_cast(a) & static_cast(b)); } -template +template [[nodiscard]] constexpr E operator^(E a, E b) noexcept { using T = std::underlying_type_t; return static_cast(static_cast(a) ^ static_cast(b)); } -template +template constexpr E& operator|=(E& a, E b) noexcept { using T = std::underlying_type_t; return a = static_cast(static_cast(a) | static_cast(b)); } -template +template constexpr E& operator&=(E& a, E b) noexcept { using T = std::underlying_type_t; return a = static_cast(static_cast(a) & static_cast(b)); } -template +template constexpr E& operator^=(E& a, E b) noexcept { using T = std::underlying_type_t; return a = static_cast(static_cast(a) ^ static_cast(b)); } -} // namespace enchantum::bitwise_operators +} // namespace bitwise_operators +} // namespace enchantum #define ENCHANTUM_DEFINE_BITWISE_FOR(Enum) \ [[nodiscard]] constexpr Enum operator&(Enum a, Enum b) noexcept { \ @@ -1556,19 +2151,20 @@ constexpr E& operator^=(E& a, E b) noexcept { return static_cast(~static_cast>(a)); \ } -#include #include #include -namespace enchantum::iostream_operators { -template +namespace enchantum { +namespace iostream_operators { +template std::basic_ostream& operator<<(std::basic_ostream& os, const E e) { return os << details::format(e); } -template - requires std::assignable_from -std::basic_istream& operator>>(std::basic_istream& is, E& value) { +template +auto operator>>(std::basic_istream& is, E& value) -> decltype((value = E{}, is)) +// sfinae to check whether value is assignable +{ std::basic_string s; is >> s; if (!is) return is; @@ -1586,35 +2182,41 @@ std::basic_istream& operator>>(std::basic_istream& i } return is; } -} // namespace enchantum::iostream_operators +} // namespace iostream_operators +} // namespace enchantum #include +#if defined(ENCAHNTUM_DETAILS_GCC_MAJOR) && ENCAHNTUM_DETAILS_GCC_MAJOR <= 10 + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + namespace enchantum { namespace details { template struct next_value_functor { - template + template [[nodiscard]] constexpr optional operator()(const E value, const std::ptrdiff_t n = 1) const noexcept { if (!enchantum::contains(value)) return optional{}; const auto index = static_cast(*enchantum::enum_to_index(value)) + (n * N); if (index >= 0 && index < static_cast(count)) - return optional{values[static_cast(index)]}; + return optional{values_generator[static_cast(index)]}; return optional{}; } }; template struct next_value_circular_functor { - template + template [[nodiscard]] constexpr E operator()(const E value, const std::ptrdiff_t n = 1) const noexcept { ENCHANTUM_ASSERT(enchantum::contains(value), "next/prev_value_circular requires 'value' to be a valid enum member", value); const auto i = static_cast(*enchantum::enum_to_index(value)); constexpr auto count = static_cast(enchantum::count); - return values[static_cast(((i + (n * N)) % count + count) % - count)]; // handles wrap around and negative n + return values_generator[static_cast(((i + (n * N)) % count + count) % + count)]; // handles wrap around and negative n } }; } // namespace details @@ -1626,18 +2228,28 @@ inline constexpr details::next_value_circular_functor<-1> prev_value_circular{}; } // namespace enchantum +#if defined(ENCAHNTUM_DETAILS_GCC_MAJOR) && ENCAHNTUM_DETAILS_GCC_MAJOR <= 10 + #pragma GCC diagnostic pop +#endif + #if __has_include() #include + #ifdef __cpp_concepts template -struct fmt::formatter : fmt::formatter { +struct fmt::formatter + #else +template +struct fmt::formatter>> + #endif + : fmt::formatter { template constexpr auto format(const E e, FmtContext& ctx) const { return fmt::formatter::format(enchantum::details::format(e), ctx); } }; -#elif __has_include() +#elif (__cplusplus >= 202002 || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002)) && __has_include() #include #include diff --git a/include/oryx/enum_bit_flags.hpp b/include/oryx/enum_bit_flags.hpp new file mode 100644 index 0000000..fed9849 --- /dev/null +++ b/include/oryx/enum_bit_flags.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +#define ORYX_DEFINE_ENUM_CLASS_BITWISE_OPERATORS(EnumType) \ + inline constexpr auto operator|(EnumType lhs, EnumType rhs)->EnumType { \ + return static_cast(std::to_underlying(lhs) | std::to_underlying(rhs)); \ + } \ + inline constexpr auto operator&(EnumType lhs, EnumType rhs)->EnumType { \ + return static_cast(std::to_underlying(lhs) & std::to_underlying(rhs)); \ + } \ + inline constexpr auto operator^(EnumType lhs, EnumType rhs)->EnumType { \ + return static_cast(std::to_underlying(lhs) ^ std::to_underlying(rhs)); \ + } \ + inline constexpr auto operator~(EnumType val)->EnumType { \ + return static_cast(~std::to_underlying(val)); \ + } \ + inline constexpr auto operator|=(EnumType& lhs, EnumType rhs)->EnumType& { return lhs = lhs | rhs; } \ + inline constexpr auto operator&=(EnumType& lhs, EnumType rhs)->EnumType& { return lhs = lhs & rhs; } \ + inline constexpr auto operator^=(EnumType& lhs, EnumType rhs)->EnumType& { return lhs = lhs ^ rhs; } diff --git a/include/oryx/error_group.hpp b/include/oryx/error_group.hpp new file mode 100644 index 0000000..3d3f2a5 --- /dev/null +++ b/include/oryx/error_group.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#include + +namespace oryx { + +class ErrorGroup { +public: + ErrorGroup() = default; + + ErrorGroup(std::initializer_list errors) + : errors_(errors) {} + + void Add(Error err) { errors_.push_back(std::move(err)); } + + [[nodiscard]] auto Size() const noexcept -> size_t { return errors_.size(); } + + [[nodiscard]] auto Empty() const noexcept -> bool { return errors_.empty(); } + + [[nodiscard]] auto Errors() const -> const std::vector& { return errors_; } + + [[nodiscard]] auto ToError() const -> Error { return Error(what()); } + + [[nodiscard]] auto what() const -> std::string { + auto what = std::format("ErrorGroup with {} error(s):\n", errors_.size()); + for (size_t i = 0; i < errors_.size(); ++i) { + what.append(std::format("[{}] {}", i, errors_[i].what())); + } + return what; + } + +private: + std::vector errors_; +}; + +} // namespace oryx \ No newline at end of file diff --git a/include/oryx/function_tracer.hpp b/include/oryx/function_tracer.hpp index 29c30ee..504f5b2 100644 --- a/include/oryx/function_tracer.hpp +++ b/include/oryx/function_tracer.hpp @@ -6,6 +6,8 @@ #include #include + #include + namespace oryx { class FunctionTracer { @@ -20,9 +22,27 @@ class FunctionTracer { std::string_view name_; }; +template +class FunctionTimingTracer { +public: + constexpr FunctionTimingTracer(const std::source_location loc = std::source_location::current()) + : name_(loc.function_name()), + sw_() {} + ~FunctionTimingTracer() { + auto elapsed = sw_.ElapsedMs(); + std::println("Function {} took {}", name_, elapsed); + } + +private: + std::string_view name_; + Stopwatch sw_; +}; + } // namespace oryx - #define ORYX_TRACE_FUNCTION() oryx::FunctionTracer __func_tracer__ + #define ORYX_TRACE_FUNCTION() oryx::FunctionTracer __oryx__func_tracer__ + #define ORYX_TRACE_FUNCTION_TIMING() oryx::FunctionTimingTracer __oryx__func_timing_tracer__ #else #define ORYX_TRACE_FUNCTION() (void)0 + #define ORYX_TRACE_FUNCTION_TIMING() (void)0 #endif \ No newline at end of file diff --git a/include/oryx/is_one_of.hpp b/include/oryx/is_one_of.hpp new file mode 100644 index 0000000..11d8648 --- /dev/null +++ b/include/oryx/is_one_of.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace oryx { + +template +inline constexpr auto IsOneOf(const T& value, const Args&... args) -> bool { + return ((value == args) || ...); +} + +} // namespace oryx \ No newline at end of file diff --git a/include/oryx/retry.hpp b/include/oryx/retry.hpp new file mode 100644 index 0000000..f808ce4 --- /dev/null +++ b/include/oryx/retry.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace oryx::retry { + +enum class ErrorKind : uint8_t { + kRetriesExhausted, +}; + +struct ExponentialConfig { + std::string name; + std::chrono::milliseconds start_backoff; + std::chrono::milliseconds max_backoff; + int max_retries; +}; + +template +inline auto ExponentialBackoff(ExponentialConfig config, F &&retryable, const std::stop_token &stoken) + -> expected { + auto current_backoff = config.start_backoff; + int num_retries = 0; + expected result; + + while (!stoken.stop_requested() && num_retries < config.max_retries) { + result = retryable(); + + if (result) { + break; + } + + std::this_thread::sleep_for(current_backoff); + current_backoff = current_backoff * 2 > config.max_backoff ? config.max_backoff : current_backoff * 2; + num_retries++; + } + + if (result) { + return result; + } + + if (stoken.stop_requested()) { + return UnexpectedError("stop requested"); + } + return UnexpectedError(std::format("{} reached max retries: {}", config.name, num_retries)); +} + +template +inline auto ExponentialBackoff(ExponentialConfig config, F &&retryable) -> expected { + std::stop_token stoken{}; + return ExponentialBackoff(config, std::forward(retryable), stoken); +} + +} // namespace oryx::retry \ No newline at end of file diff --git a/include/oryx/scope_exit.hpp b/include/oryx/scope_exit.hpp new file mode 100644 index 0000000..8fde757 --- /dev/null +++ b/include/oryx/scope_exit.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include + +namespace oryx { + +template +class ScopeExit { +public: + explicit ScopeExit(F&& fn) + : fn_(std::forward(fn)) {} + + ~ScopeExit() { + if (is_owner_) { + fn_(); + } + } + + void Release() { + is_owner_ = false; // NOTE: Should this return the function object + } + + ScopeExit(ScopeExit&& other) = delete; + ScopeExit(const ScopeExit&) = delete; + auto operator=(const ScopeExit&) -> ScopeExit& = delete; + +private: + F fn_; + bool is_owner_{true}; +}; + +} // namespace oryx \ No newline at end of file diff --git a/include/oryx/spsc_queue.hpp b/include/oryx/spsc_queue.hpp index 186c163..a33d278 100644 --- a/include/oryx/spsc_queue.hpp +++ b/include/oryx/spsc_queue.hpp @@ -30,7 +30,7 @@ namespace folly { */ template struct ProducerConsumerQueue { - typedef T value_type; + using value_type = T; ProducerConsumerQueue(const ProducerConsumerQueue&) = delete; ProducerConsumerQueue& operator=(const ProducerConsumerQueue&) = delete; diff --git a/include/oryx/synchronized.hpp b/include/oryx/synchronized.hpp index a6521ca..c7c985b 100644 --- a/include/oryx/synchronized.hpp +++ b/include/oryx/synchronized.hpp @@ -2,18 +2,14 @@ #include -namespace oryx { +#include -template -concept basic_lockable = requires(L m) { - m.lock(); - m.unlock(); -}; +namespace oryx { -template +template class synchronized; -template +template class update_guard { std::unique_lock lock_; GuardedType *guarded_data_; @@ -35,7 +31,7 @@ class update_guard { auto operator*() const noexcept -> GuardedType & { return *guarded_data_; } }; -template +template class synchronized { friend class update_guard; friend class update_guard; diff --git a/include/oryx/traits.hpp b/include/oryx/traits.hpp new file mode 100644 index 0000000..6a3fdae --- /dev/null +++ b/include/oryx/traits.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +namespace oryx::traits { + +template +struct is_std_function : std::false_type {}; + +template +struct is_std_function> : std::true_type {}; + +template +inline constexpr bool is_std_function_v = is_std_function::value; + +template +concept BasicLockable = requires(L m) { + m.lock(); + m.unlock(); +}; + +} // namespace oryx::traits \ No newline at end of file diff --git a/include/oryx/unique_file_ptr.hpp b/include/oryx/unique_file_ptr.hpp new file mode 100644 index 0000000..bd64da1 --- /dev/null +++ b/include/oryx/unique_file_ptr.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +namespace oryx { +namespace detail { + +struct FileDeleter { + auto operator()(FILE* file) const { fclose(file); }; +}; + +} // namespace detail + +using UniqueFilePtr = std::unique_ptr; + +inline auto OpenFile(std::string_view file_name, std::string_view modes) { + return UniqueFilePtr{fopen(file_name.data(), modes.data())}; +} + +} // namespace oryx \ No newline at end of file diff --git a/include/oryx/uuid.hpp b/include/oryx/uuid.hpp new file mode 100644 index 0000000..b675be6 --- /dev/null +++ b/include/oryx/uuid.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace oryx::uuid4 { + +auto Generate() -> std::string; + +} // namespace oryx::uuid4 \ No newline at end of file diff --git a/src/uuid.cpp b/src/uuid.cpp new file mode 100644 index 0000000..bf4b7e1 --- /dev/null +++ b/src/uuid.cpp @@ -0,0 +1,38 @@ +#include + +#include +#include +#include +#include +#include + +namespace oryx { + +auto uuid4::Generate() -> std::string { + std::random_device rd; + std::uniform_int_distribution dis(0, 255); + + // generate 16 random bytes + std::array bytes; + std::ranges::generate(bytes, [&dis, &rd] { return static_cast(dis(rd)); }); + + // set version to 4 + bytes[6] = (bytes[6] & 0x0F) | 0x40; + + // set variant to RFC 4122 + bytes[8] = (bytes[8] & 0x3F) | 0x80; + + return std::format( + "{0:02x}{1:02x}{2:02x}{3:02x}-" + "{4:02x}{5:02x}-" + "{6:02x}{7:02x}-" + "{8:02x}{9:02x}-" + "{10:02x}{11:02x}{12:02x}{13:02x}{14:02x}{15:02x}", + static_cast(bytes[0]), static_cast(bytes[1]), static_cast(bytes[2]), static_cast(bytes[3]), + static_cast(bytes[4]), static_cast(bytes[5]), static_cast(bytes[6]), static_cast(bytes[7]), + static_cast(bytes[8]), static_cast(bytes[9]), static_cast(bytes[10]), + static_cast(bytes[11]), static_cast(bytes[12]), static_cast(bytes[13]), + static_cast(bytes[14]), static_cast(bytes[15])); +} + +} // namespace oryx \ No newline at end of file diff --git a/tests/chrono_mock_clock.hpp b/tests/chrono_mock_clock.hpp new file mode 100644 index 0000000..6db427b --- /dev/null +++ b/tests/chrono_mock_clock.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace oryx { + +struct ChronoMockClock { + using duration = std::chrono::nanoseconds; + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point; + + static constexpr bool is_steady = true; + + static auto now() noexcept -> time_point { return now_; } + + static time_point now_; +}; + +ChronoMockClock::time_point ChronoMockClock::now_ = {}; + +static_assert(std::chrono::is_clock_v, "ChronoMockClock does not satisfy chrono clock!"); + +} // namespace oryx \ No newline at end of file diff --git a/tests/cycle_timer.cpp b/tests/cycle_timer.cpp new file mode 100644 index 0000000..e69de29 diff --git a/tests/error_group_test.cpp b/tests/error_group_test.cpp new file mode 100644 index 0000000..f020550 --- /dev/null +++ b/tests/error_group_test.cpp @@ -0,0 +1,3 @@ +#include "doctest.hpp" + +#include diff --git a/tests/is_one_off_test.cpp b/tests/is_one_off_test.cpp new file mode 100644 index 0000000..3db1298 --- /dev/null +++ b/tests/is_one_off_test.cpp @@ -0,0 +1,29 @@ +#include "doctest.hpp" + +#include + +#include + +using namespace oryx; + +namespace { + +enum class Test : uint8_t { kProp1, kProp2, kProp3 }; + +} + +TEST_CASE("IsOneOf boolean") { + bool value = true; + CHECK(IsOneOf(value, false, false, true, false)); + CHECK_FALSE(IsOneOf(value, false, false, false)); + + value = false; + CHECK(IsOneOf(value, true, true, false, true)); + CHECK_FALSE(IsOneOf(value, true, true, true)); +} + +TEST_CASE("IsOneOf enum") { + Test value = Test::kProp1; + CHECK(IsOneOf(value, Test::kProp2, Test::kProp1)); + CHECK_FALSE(IsOneOf(value, Test::kProp2, Test::kProp3)); +} \ No newline at end of file diff --git a/tests/retry_test.cpp b/tests/retry_test.cpp new file mode 100644 index 0000000..72b9812 --- /dev/null +++ b/tests/retry_test.cpp @@ -0,0 +1,52 @@ +#include "doctest.hpp" + +#include + +using namespace std::chrono_literals; +using namespace oryx; + +namespace { +const retry::ExponentialConfig kDefaultConfig{.name = "test", .start_backoff = 0s, .max_backoff = 0s, .max_retries = 1}; +} + +TEST_CASE("Retryable fails and succeeds") { + CHECK_FALSE( + retry::ExponentialBackoff(kDefaultConfig, []() -> void_expected { return UnexpectedError(""); })); + CHECK(retry::ExponentialBackoff(kDefaultConfig, []() -> void_expected { return kVoidExpected; })); +} + +TEST_CASE("Retryable succeeds after some tries") { + int tries = 4; + int tries_so_far = 0; + const auto retryable = [&]() -> void_expected { + tries_so_far++; + + if (tries_so_far == tries) { + return kVoidExpected; + } + + return UnexpectedError(""); + }; + + auto config = kDefaultConfig; + config.max_retries = tries; + CHECK(retry::ExponentialBackoff(config, retryable)); +} + +TEST_CASE("Exhausting max retries") { + int tries = 4; + int tries_so_far = 0; + const auto retryable = [&]() -> void_expected { + tries_so_far++; + + if (tries_so_far == tries) { + return kVoidExpected; + } + + return UnexpectedError(""); + }; + + auto config = kDefaultConfig; + config.max_retries = tries - 1; + CHECK_FALSE(retry::ExponentialBackoff(config, retryable)); +} \ No newline at end of file diff --git a/tests/scope_exit.cpp b/tests/scope_exit.cpp new file mode 100644 index 0000000..e69de29 diff --git a/tests/stopwatch_test.cpp b/tests/stopwatch_test.cpp index 446020a..fc61597 100644 --- a/tests/stopwatch_test.cpp +++ b/tests/stopwatch_test.cpp @@ -1,20 +1,27 @@ #include "doctest.hpp" -#include +#include #include +#include "chrono_mock_clock.hpp" + using namespace oryx::chrono; -namespace { -constexpr std::chrono::milliseconds kErrorMargin(100); +TEST_CASE("Reset calls clock now") { + details::StopwatchImpl sw{}; + const auto tp = oryx::ChronoMockClock::time_point{std::chrono::seconds(5)}; + oryx::ChronoMockClock::now_ = tp; + sw.Reset(); + CHECK_EQ(sw.GetStart(), tp); } -TEST_CASE("Stopwatch should measure elapsed time") { - std::chrono::milliseconds sleep_time(100); - Stopwatch sw{}; - std::this_thread::sleep_for(sleep_time); - auto elapsed = sw.ElapsedMs(); - CHECK_GE(elapsed, sleep_time); - CHECK_LT(elapsed, sleep_time + kErrorMargin); +TEST_CASE("Elapsed and elapsed ms subtracts now from start") { + details::StopwatchImpl sw{oryx::ChronoMockClock::time_point{std::chrono::seconds(5)}}; + const auto tp = oryx::ChronoMockClock::time_point{std::chrono::seconds(10)}; + oryx::ChronoMockClock::now_ = tp; + CHECK_EQ(sw.ElapsedMs(), std::chrono::milliseconds(5000)); + + oryx::ChronoMockClock::now_ = tp; + CHECK_EQ(sw.Elapsed(), std::chrono::nanoseconds(5000000000)); } \ No newline at end of file diff --git a/tests/traits_test.cpp b/tests/traits_test.cpp new file mode 100644 index 0000000..71d2970 --- /dev/null +++ b/tests/traits_test.cpp @@ -0,0 +1,33 @@ +#include "doctest.hpp" + +#include +#include +#include + +using namespace oryx; + +namespace { + +template +struct Test {}; + +template +struct is_basic_lockable : std::false_type {}; + +template +struct is_basic_lockable : std::true_type {}; + +} // namespace + +TEST_CASE("std::function satisfies is_std_function") { + CHECK(traits::is_std_function>{}); + CHECK(traits::is_std_function_v>); +} + +TEST_CASE("lambda does not satisfy is_std_function") { + CHECK_FALSE(traits::is_std_function_v void { int x; })>); +} + +TEST_CASE("std::mutex satisfies basic lockable") { CHECK(is_basic_lockable{}); } + +TEST_CASE("std::function does not satisfy basic lockable") { CHECK_FALSE(is_basic_lockable>{}); } \ No newline at end of file diff --git a/tests/unique_file_ptr_test.cpp b/tests/unique_file_ptr_test.cpp new file mode 100644 index 0000000..c2e45bf --- /dev/null +++ b/tests/unique_file_ptr_test.cpp @@ -0,0 +1,49 @@ +#include "doctest.hpp" + +#include + +#include + +using namespace oryx; + +namespace { + +struct TempFile { + TempFile() + : file(std::filesystem::temp_directory_path()) { + file.append("tmp.txt"); + } + + ~TempFile() { std::filesystem::remove_all(file); } + + auto ToString() { return file.string(); } + + std::filesystem::path file; +}; + +} // namespace + +TEST_CASE("Open file works") { + TempFile tmp_file{}; + const auto file_name = tmp_file.ToString(); + + std::string_view data{"Hello World"}; + + { + auto file_ptr = OpenFile(file_name.c_str(), "w"); + CHECK(file_ptr); + int bytes_written = fwrite(data.data(), sizeof(std::string_view::value_type), data.size(), file_ptr.get()); + CHECK_EQ(data.size(), bytes_written); + } + + { + auto file_ptr = OpenFile(file_name.c_str(), "r"); + CHECK(file_ptr); + char buffer[12]; + int bytes_read = fread(buffer, sizeof(char), 12, file_ptr.get()); + buffer[11] = '\0'; + CHECK_EQ(data.size(), bytes_read); + std::string_view read_data{buffer}; + CHECK_EQ(read_data, data); + } +} \ No newline at end of file