diff --git a/CMakeLists.txt b/CMakeLists.txt index e6a06e03..7377db2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,28 +33,30 @@ project (conjure_enum LANGUAGES CXX HOMEPAGE_URL https://github.com/fix8mt/conjure_enum DESCRIPTION "Lightweight C++20 enum and typename reflection" - VERSION 1.0.0) + VERSION 1.1.0) + +message("${CMAKE_PROJECT_NAME} version ${CMAKE_PROJECT_VERSION}") # to disable strip: # cmake -DBUILD_STRIP_EXE=false .. option(BUILD_STRIP_EXE "enable stripping of non-unittest executables" true) -message("-- Build strip exes: ${BUILD_STRIP_EXE}") +message("-- Build: strip exes ${BUILD_STRIP_EXE}") # to disable warnings: # cmake -DBUILD_ALL_WARNINGS=false .. option(BUILD_ALL_WARNINGS "enable building with all warnings" true) -message("-- Build with all warnings : ${BUILD_ALL_WARNINGS}") +message("-- Build: with all warnings ${BUILD_ALL_WARNINGS}") # to disable building unit tests: # cmake -DBUILD_UNITTESTS=false .. option(BUILD_UNITTESTS "enable building unit tests" true) -message("-- Build unit tests: ${BUILD_UNITTESTS}") +message("-- Build: unit tests ${BUILD_UNITTESTS}") # to enable clang build profiler: # cmake -DBUILD_CLANG_PROFILER=true .. # see examples/cbenchmark.sh option(BUILD_CLANG_PROFILER "enable clang build profiler" false) -message("-- Build clang profiler: ${BUILD_CLANG_PROFILER}") +message("-- Build: clang profiler ${BUILD_CLANG_PROFILER}") if(BUILD_CLANG_PROFILER) if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") add_compile_options(-ftime-trace) diff --git a/README.md b/README.md index c99154e1..86b6c295 100644 --- a/README.md +++ b/README.md @@ -42,21 +42,24 @@ # 1. Quick links -||**Link**|**Description**| ---|--|-- -|1|[Implementation](include/fix8/conjure_enum.hpp)| For header implementation| -|2|[`conjure_enum` API and Examples](#3-api-and-examples-using-conjure_enum)| General examples| -|3|[`enum_bitset` API and Examples](#4-api-and-examples-using-enum_bitset)| enhanced enum aware `std::bitset`| -|4|[`conjure_type` API and Examples](#5-api-and-examples-using-conjure_type)| any type string extractor| -|5|[Building](#6-building)| How to build or include| +|1|[`conjure_enum`](#3-conjure_enum)| API and examples| +|:--|:--|:--| +|2|[`enum_bitset`](#4-enum_bitset)| Enhanced enum aware `std::bitset`| +|3|[`conjure_type`](#5-conjure_type)| Any type string extractor| +|4|[`fixed_string`](#6-fixed_string)| Statically stored null terminated fixed string| +|5|[Building](#7-building)| How to build or include| |6|[vcpkg](https://vcpkg.io/en/package/conjure-enum)| For vcpkg package| -|7|[Notes](#7-notes)| Notes on the implementation, limits, etc| -|8|[Compilers](#8-compiler-support)| Supported compilers| -|9|[Compiler issues](#9-compiler-issues)| Workarounds for various compiler issues| -|10|[Results of `std::source_location`](reference/source_location.md)| For implementation specific `std::source_location` results| +|7|[Notes](#8-notes)| Notes on the implementation, limits, etc| +|8|[Benchmarks](#9-benchmarks)| Benchmarking | +|9|[Compilers](#10-compiler-support)| Supported compilers| +|10|[Compiler issues](#11-compiler-issues)| Workarounds for various compiler issues| +|11|[Results of `std::source_location`](reference/source_location.md)| For implementation specific `std::source_location` results| > [!TIP] > Use the built-in [table of contents](https://github.blog/changelog/2021-04-13-table-of-contents-support-in-markdown-files/) to navigate this guide. > Even better in [full read view](./README.md) of this page. +> +> For the latest cutting edge changes, see the [dev branch](https://github.com/fix8mt/conjure_enum/tree/dev). +> You can view the changes (if any) [here](https://github.com/fix8mt/conjure_enum/compare/master..dev). --- # 2. Introduction @@ -66,12 +69,10 @@ Based on the awesome work in [`magic_enum`](https://github.com/Neargye/magic_enu this library offers a streamlined and powerful way to add reflection capabilities to your C++ enums and other types. We've optimized the core functionality, focusing on the main features developers usually want. We've also added general purpose typename reflection for any type. -## b) Embracing C++20 +`conjure_enum`[^1] takes full advantage of recently added C++20 features. We've leveraged the convenience of [`std::source_location`](https://en.cppreference.com/w/cpp/utility/source_location) and +unlocked the potential of [`constexpr` algorithms](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0202r3.html) and [concepts](https://en.cppreference.com/w/cpp/language/constraints). -`conjure_enum`[^1] takes full advantage of recently added C++20 features. We've leveraged the convenience of `std::source_location` and -unlocked the potential of `constexpr` algorithms and concepts. - -## c) Key Benefits +## b) Highlights - ***Single Header-Only***: No external dependencies, simplifying integration into your project - ***Modern C++20***: Entirely `constexpr` for compile-time safety, efficiency and performance; no macros @@ -79,16 +80,16 @@ unlocked the potential of `constexpr` algorithms and concepts. - scoped and unscoped enums - enums with **aliases** and **gaps** - anonymous and named namespaced enums and types - - custom enum ranges + - custom [enum ranges](#ii-using-enum_range) - ***Easy to Use***: Class-based approach with intuitive syntax -- ***Convenient***: `enum_bitset` provides an enhanced enum aware `std::bitset` (see 3 above) -- ***Useful***: `conjure_type` gives you the type string of _any type!_ (see 4 above) -- ***Wide Compiler Compatibility***: Support for: (see 7 above) +- ***Convenient***: `enum_bitset` provides an enhanced enum aware `std::bitset` (see 2 above) +- ***Useful***: `conjure_type` gives you the type string of _any type!_ (see 3 above) +- ***Wide Compiler Compatibility***: Support for: (see 9 above) - GCC - Clang - MSVC - XCode/Apple Clang -- ***Confidence in Quality***: Includes comprehensive unit test suite for reliable functionality and profiling +- ***Confidence***: Includes comprehensive unit test suite for reliable functionality and profiling - ***Expanded***: Enhanced API: - `add_scope` - `remove_scope` @@ -96,10 +97,10 @@ unlocked the potential of `constexpr` algorithms and concepts. - `for_each_n` - `dispatch` - iterators and more! -- ***Transparency***: Compiler implementation variability fully documented, verifiable and reportable (see 9 above) +- ***Transparency***: Compiler implementation variability fully documented, verifiable and reportable (see 11 above) --- -# 3. API and Examples using `conjure_enum` +# 3. `conjure_enum` All examples refer to the following enums: ```c++ enum class component { scheme, authority, userinfo, user, password, host, port, path=12, test=path, query, fragment }; @@ -107,9 +108,22 @@ enum component1 { scheme, authority, userinfo, user, password, host, port, path= enum class numbers { zero, one, two, three, four, five, six, seven, eight, nine }; ``` +> [!IMPORTANT] +> Your type _must_ be an enum, satisfying: +> ```C++ +>template +>concept valid_enum = requires(T) +>{ +> requires std::same_as>; +> requires std::is_enum_v; +>}; +>``` + ## a) `enum_to_string` ```c++ static constexpr std::string_view enum_to_string(T value, bool noscope=false); +template +static constexpr std::string_view enum_to_string(); ``` Returns a `std::string_view` or empty if not found. Optionally passing `true` will remove scope in result if present. `noscope` option ![](assets/notminimalred.svg). @@ -119,6 +133,7 @@ auto name_trim { conjure_enum::enum_to_string(component::path, true) auto alias_name { conjure_enum::enum_to_string(component::test) }; // alias auto noscope_name { conjure_enum::enum_to_string(path) }; std::cout << name << '\n' << name_trim << '\n' << alias_name << '\n' << noscope_name << '\n'; +std::cout << conjure_enum::enum_to_string() << '\n'; ``` _output_ ```CSV @@ -126,12 +141,13 @@ component::path path component::path path +numbers::two ``` ### Aliases Because all methods in `conjure_enum` are defined _within_ a `class` instead of individual template functions in a `namespace`, you can reduce your typing with standard aliases: ```c++ -using ec = conjure_enum; +using ec = FIX8::conjure_enum; std::cout << std::format("\"{}\"\n", ec::enum_to_string(component::authority)); std::cout << std::format("\"{}\"\n", ec::enum_to_string(static_cast(100))); ``` @@ -187,23 +203,26 @@ _output_ 12 100 <-- invalid, error value ``` -## d) `int_to_enum` +## d) `int_to_enum`, `enum_cast` ```c++ static constexpr std::optional int_to_enum(int value); +static constexpr T enum_cast(int value); ``` Returns a `std::optional`. Empty if value was not valid. Use `std::optional::value_or()` to set an error value -and avoid throwing an exception. +and avoid throwing an exception. `enum_cast` will cast to the enum type regardless of whether the value is a valid enum. ```c++ int value { static_cast(conjure_enum::int_to_enum(12).value()) }; int noscope_value { static_cast(conjure_enum::int_to_enum(12).value()) }; int bad_value { static_cast(conjure_enum::int_to_enum(100).value_or(component(100))) }; std::cout << value << '\n' << noscope_value << '\n' << bad_value << '\n'; +std::cout << static_cast(conjure_enum::enum_cast(150)) << '\n'; ``` _output_ ```CSV 12 12 100 <-- invalid, error value +150 <-- invalid, but still casted ``` ## e) `enum_to_int`, `enum_to_underlying` ```c++ @@ -237,7 +256,7 @@ _output_ ```CSV 10 ``` -## g) `names` +## g) `names` ![](assets/notminimalred.svg) ```c++ static constexpr std::array names; ``` @@ -400,22 +419,52 @@ userinfo component::userinfo static constexpr std::array, std::size_t> rev_scoped_entries; ``` Same as `scoped_entries` except reversed, sorted by scoped name. Use to lookup unscoped name. -## m) `contains` +## m) `index` +```c++ +static constexpr std::optional index(T value); +template +static constexpr std::optional index(); +``` +Returns the index (position in 0 based array of values) of the supplied enum value as an `std::optional`. +Empty if value was not valid. Use `std::optional::value_or()` to set an error value +and avoid throwing an exception. +```c++ +std::cout << conjure_enum::index(component::password).value() << '\n'; +std::cout << conjure_enum::index(component(100)).value_or(100) << '\n'; +std::cout << conjure_enum::index().value() << '\n'; +std::cout << conjure_enum::index().value_or(100) << '\n'; +``` +_output_ +```CSV +4 +100 <-- invalid, error value +4 +100 <-- invalid, error value +``` +## n) `contains`, `is_valid` ```c++ static constexpr bool contains(T value); static constexpr bool contains(std::string_view str); +template +static constexpr bool contains(); +template +static constexpr bool is_valid(); ``` Returns `true` if the enum contains the given value or string. ```c++ std::cout << std::format("{}\n", conjure_enum::contains(component::path)); std::cout << std::format("{}\n", conjure_enum::contains("nothing")); +std::cout << std::format("{}\n", conjure_enum::contains()); +std::cout << std::format("{}\n", conjure_enum::is_valid()); ``` _output_ ```CSV true false +true +true ``` -## n) `for_each`, `for_each_n` +## o) `for_each`, `for_each_n` ![](assets/notminimalred.svg) ```c++ template requires std::invocable @@ -434,13 +483,20 @@ requires std::invocable [[maybe_unused]] static constexpr auto for_each_n(int n, Fn&& func, C *obj, Args&&... args); ``` Call supplied invocable for _each_ enum value. Similar to `std::for_each` except the first parameter of your invocable must accept an enum value (passed by `for_each`). -Optionally provide any additional parameters. Works with lambdas, member functions, functions etc. You can limit the number of calls to your -invocable by using the `for_each_n` version with the first parameter being the maximum number to call. The second version of `for_each` and `for_each_n` is intended to be used +Optionally provide any additional parameters. You can limit the number of calls to your invocable by using the `for_each_n` version with the first parameter +being the maximum number to call. The second version of `for_each` and `for_each_n` is intended to be used when using a member function - the _second_ parameter passed by your call must be the `this` pointer of the object. If you wish to pass a `reference` parameter, you must wrap it in `std::ref`. -Returns `std::bind(std::forward(func), std::placeholders::_1, std::forward(args)...)` -or `std::bind(std::forward(func), obj, std::placeholders::_1, std::forward(args)...)` which can be stored or immediately invoked. +Works with lambdas, member functions, functions etc, compatible with `std::invoke`. + +Returns +```c++ +std::bind(std::forward(func), std::placeholders::_1, std::forward(args)...); +// or +std::bind(std::forward(func), obj, std::placeholders::_1, std::forward(args)...); +``` +which can be stored or immediately invoked. See `enum_bitset::for_each` to iterate through a bitset. ```c++ @@ -519,7 +575,7 @@ _output_ ```CSV 160 ``` -## o) `dispatch` +## p) `dispatch` ![](assets/notminimalred.svg) ```c++ template static constexpr bool tuple_comp(const std::tuple& pl, const std::tuple& pr); @@ -540,26 +596,35 @@ template // specialisa requires (std::invocable && I > 0) static constexpr void dispatch(T ev, const std::array, I>& disp, C *obj, Args&&... args); ``` -With a given enum, search and call user supplied invocable. Use this method where complex event handling is required, allowing you to easily declare predefined invocable actions +With a given enum, search and call user supplied invocable. A typical use case would be where you want to demux a complex event, allowing you to easily declare predefined invocable actions for different enum values. -Where invocable returns a value, return this value or a user supplied "not found" value. -Where invocable is void, call user supplied "not found" invocable. +- Where invocable returns a value, return this value or a user supplied "not found" value. +- Where invocable is void, call user supplied "not found" invocable. + The first parameter of your invocable must accept an enum value (passed by `dispatch`). -Optionally provide any additional parameters. Works with lambdas, member functions, functions etc. +Optionally provide any additional parameters. + +Works with lambdas, member functions, functions etc, compatible with `std::invoke`. There are two versions of `dispatch` - the first takes an enum value, a 'not found' value, and a `std::array` of `std::tuple` of enum and invocable. The second version takes an enum value, and a `std::array` of `std::tuple` of enum and invocable. The last element of the array is called if the enum is not found. This version is intended for use with `void` return invocables. + The second version of each of the above is intended to be used when using a member function - the _first_ parameter passed after your array must be the `this` pointer of the object. You can also use `std::bind` to bind the this pointer and any parameter placeholders when declaring your array. If you wish to pass a `reference` parameter, you must wrap it in `std::ref`. -If you wish to provide a `constexpr` array, you will need to use a simple function prototype, since `std::function` is not constexpr (see unit tests for examples). +> [!TIP] +> If you wish to provide a `constexpr` array, you will need to use a simple function prototype, since `std::function` is not constexpr (see unit tests for examples). + > [!IMPORTANT] > Your `std::array` of `std::tuple` should be sorted by enum. -> The `dispatch` method performs a binary search on the array. Complexity for a sorted array is at most $2log_2(N)+O(1)$ comparisons. +> The `dispatch` method performs a binary search on the array. Complexity for a sorted array is at most  $2log_2(N)+O(1)$  comparisons. > If the array is _not_ sorted, complexity is linear. + +The following example uses a `static constexpr` array of pointers to functions. For brevity they all point to the same function except the last which is +a lambda. ```c++ enum class directions { left, right, up, down, forward, backward, notfound=-1 }; static constexpr auto prn([](directions ev) { std::cout << conjure_enum::enum_to_string(ev) << '\n'; }); @@ -576,15 +641,34 @@ static constexpr auto tarr }) }; conjure_enum::dispatch(directions::right, tarr); +conjure_enum::dispatch(directions::down, tarr); conjure_enum::dispatch(directions::forward, tarr); std::cout << conjure_enum::enum_to_int(directions::notfound) << '\n'; ``` _output_ ```CSV directions::right +directions::down not found: directions::forward -1 ``` +This example uses lambdas: +```c++ +const auto dd1 +{ + std::to_array>> + ({ + { component::scheme, [](component ev, int a) { return a * 100 + conjure_enum::enum_to_int(ev); } }, + { component::port, [](component ev, int a) { return a * 200 + conjure_enum::enum_to_int(ev); } }, + { component::fragment, [](component ev, int a) { return a * 300 + conjure_enum::enum_to_int(ev); } }, + }) +}; +std::cout << conjure_enum::dispatch(component::port, -1, dd1, 10) << '\n'; +``` +_output_ +```CSV +2006 +``` This example uses member functions: ```c++ struct foo @@ -618,10 +702,10 @@ _output_ 6000 1015 ``` -## p) `is_scoped` +## q) `is_scoped` ```c++ -struct is_scoped : std::integral_constant>; }>{}; +struct is_scoped : std::bool_constant>; }>{}; ``` Returns `true` if the specified enum type is scoped. ```c++ @@ -633,22 +717,21 @@ _output_ true false ``` -## q) `is_valid` +## r) `is_continuous` ```c++ -template -static constexpr bool is_valid(); +static constexpr bool is_continuous(); ``` -Returns `true` if enum value is valid. +Returns `true` if enum range is continuous (no gaps). ```c++ -std::cout << std::format("{}\n", conjure_enum::is_valid()); -std::cout << std::format("{}\n", conjure_enum::is_valid(16)>()); +std::cout << std::format("{}\n", conjure_enum::is_continuous()); +std::cout << std::format("{}\n", conjure_enum::is_continuous()); ``` _output_ ```CSV true false ``` -## r) `type_name` +## s) `type_name` ![](assets/notminimalred.svg) ```c++ static constexpr std::string_view type_name(); ``` @@ -662,7 +745,7 @@ _output_ component component1 ``` -## s) `remove_scope` ![](assets/notminimalred.svg) +## t) `remove_scope` ![](assets/notminimalred.svg) ```c++ static constexpr std::string_view remove_scope(std::string_view what); ``` @@ -676,7 +759,7 @@ _output_ path path ``` -## t) `add_scope` ![](assets/notminimalred.svg) +## u) `add_scope` ![](assets/notminimalred.svg) ```c++ static constexpr std::string_view add_scope(std::string_view what); ``` @@ -690,7 +773,7 @@ _output_ component::path path ``` -## u) `has_scope` ![](assets/notminimalred.svg) +## v) `has_scope` ![](assets/notminimalred.svg) ```c++ static constexpr bool has_scope(std::string_view what); ``` @@ -706,7 +789,7 @@ true false false ``` -## v) `iterators` +## w) `iterators` ![](assets/notminimalred.svg) ```c++ static constexpr auto cbegin(); static constexpr auto cend(); @@ -733,7 +816,7 @@ _output_ 8 numbers::eight 9 numbers::nine ``` -## w) `iterator_adaptor` +## x) `iterator_adaptor` ![](assets/notminimalred.svg) ```c++ template struct iterator_adaptor; @@ -756,7 +839,7 @@ _output_ 8 9 ``` -## x) `front, back` +## y) `front, back` ![](assets/notminimalred.svg) ```c++ static constexpr auto front(); static constexpr auto back(); @@ -764,16 +847,15 @@ static constexpr auto back(); These methods return `*cbegin()` and `*std::prev(cend())` respectively all from `entries` defined above. ```c++ -using en = conjure_enum; -std::cout << static_cast(std::get<0>(en::front())) << ' ' << std::get<1>(en::front()) << '\n'; -std::cout << static_cast(std::get<0>(en::back())) << ' ' << std::get<1>(en::back()) << '\n'; +for (const auto& [ev,str] : {conjure_enum::front(), conjure_enum::back()}) + std::cout << static_cast(ev) << ' ' << str << '\n'; ``` _output_ ```CSV 0 numbers::zero 9 numbers::nine ``` -## y) `ostream_enum_operator` +## z) `ostream_enum_operator` ![](assets/notminimalred.svg) ```c++ template, valid_enum T> constexpr std::basic_ostream& operator<<(std::basic_ostream& os, T value); @@ -795,7 +877,7 @@ _output_ "host" "100" ``` -## z) `epeek, tpeek` +## A) `epeek, tpeek` ```c++ static consteval const char *tpeek(); template @@ -815,27 +897,69 @@ static consteval const char* FIX8::conjure_enum::tpeek() [with T = component] static consteval const char* FIX8::conjure_enum::epeek() [with T e = component::path; T = component] ``` -## aa) `get_enum_min_value` and `get_enum_max_value` +## B) `get_enum_min_value`, `get_enum_max_value`, `get_actual_enum_min_value` and `get_actual_enum_max_value` ```c++ static constexpr int get_enum_min_value(); static constexpr int get_enum_max_value(); +static constexpr int get_actual_enum_min_value(); +static constexpr int get_actual_enum_max_value(); ``` -These functions return the min and max enum range for the specified enum. If you have specialised `enum_range` then these values +The first two functions return the min and max enum range for the specified enum. If you have specialised `enum_range` then these values will be reported (see below). + +The second two functions return the actual min and max enum values as ints for the specified enum. ```c++ std::cout << conjure_enum::get_enum_min_value() << '/' << conjure_enum::get_enum_min_value() << '\n'; +std::cout << conjure_enum::get_actual_enum_min_value() << '/' << conjure_enum::get_actual_enum_min_value() << '\n'; ``` _output_ ```CSV -128/127 +0/14 ``` + +## C) `in_range` +```c++ +static constexpr bool in_range(T value); +``` +Returns `true` if the given value is within the minimum and maximum defined values for this enum type. +```c++ +std::cout << std::format("{}\n", conjure_enum::in_range(static_cast(100))); +``` +_output_ +```CSV +false +``` + --- -# 4. API and Examples using `enum_bitset` +# 4. `enum_bitset` `enum_bitset` is a convenient way of creating bitsets based on `std::bitset`. It uses your enum (scoped or unscoped) for the bit positions (and names). -> [!WARNING] -> Your enum _must_ be continuous. The last value must be less than the count of enumerations. -> We decided on this restriction for both simplicity and practicality - bitsets only really make sense when represented in this manner. +> [!NOTE] +> - Your enum sequence _must_ be 0 based +> - Continuous +> - The last value must be less than the count of enumerations +> +> We decided on these restrictions for both simplicity and practicality - bitsets only really make sense when represented in this manner; also... +> +> - This implementation is limited to 64 bits (arbitrary length impl. soon). + +> [!IMPORTANT] +> You must include +> ```C++ +> #include +> #include +> ``` +> Your enum _must_ satisfy the following: +> ```C++ +>template +>concept valid_bitset_enum = valid_enum and requires(T) +>{ +> requires conjure_enum::is_continuous(); +> requires conjure_enum::get_actual_enum_min_value() == 0; +> requires conjure_enum::get_actual_enum_max_value() < conjure_enum::count(); +>}; +>``` ## a) Creating an `enum_bitset` ```c++ @@ -843,6 +967,7 @@ constexpr enum_bitset() = default; constexpr enum_bitset(U bits); constexpr enum_bitset(std::string_view from, bool anyscope=false, char sep='|', bool ignore_errors=true); +constexpr enum_bitset(std::bitset from); template constexpr enum_bitset(E... comp); @@ -912,6 +1037,17 @@ _output_ ```CSV exception: twenty ``` +You can also create an `enum_bitset` from a `std::bitset` of the same number of bits. +```c++ +std::bitset<10> bs{1 << 1 | 1 << 3 | 1 << 6}; +enum_bitset ed(bs); +std::cout << ed << '\n'; +``` +_output_ +```CSV +0001001010 +``` + ## b) Standard bit operators All of the standard operators are supported. Assignment operators return a `enum_bitset&`, non-assignment operators return a `enum_bitset`. @@ -955,7 +1091,7 @@ All of the standard accessors and mutators are supported. | `to_ullong` | convert to `unsigned long long` | | `count` | count of bits on | | `size` | number of bits in bitset | -| `operator[]` | test bit at position | +| `operator[]` | set or test bit at position or enum value | | `any` | return `true` if any bit is on | | `all` | return `true` if all bits are on | | `none` | return `true` if no bits are on | @@ -965,12 +1101,22 @@ Additional methods | :--- | :--- | | `set` | set all specified bits, templated | | `reset` | reset all specified bits, templated | +| `rotl` | rotate left specified times| +| `rotr` | rotate right specified times| +| `countl_zero` | counts number of consecutive `0` bits, starting from the most significant bit | +| `countl_one` | counts number of consecutive `1` bits, starting from the most significant bit | +| `countr_zero` | counts number of consecutive `0` bits, starting from the least significant bit | +| `countr_one` | counts number of consecutive `1` bits, starting from the least significant bit | | `any_of` | test for one or more bits, templated, function, types and underlyings | | `all_of` | test for all specified bits, templated, function, types and underlyings | | `none_of` | test for all specified bits set to off, templated, function, types and underlyings | | `not_count` | complement of count, count of off bits | +| `has_single_bit` | return true if bitset is an integral power of two| + +> [!NOTE] +> `rotl`, `rotl`, `countl*` and `countr*` operate on the _used_ bits of the underlying type. -Take a look at the [implementation](include/fix8/conjure_enum.hpp) for more detail on the various API functions available. +Take a look at the [implementation](include/fix8/conjure_enum_bitset.hpp) for more detail on the various API functions available. You can also review the unit test cases for examples of use. All accessors and mutators work with enum values or integers as with operators. They also work with multiple values, either as template parameters or @@ -987,6 +1133,11 @@ std::cout << std::boolalpha << eb.all_of(numbers::zero,numbers::nine) << '\n'; std::cout << eb << '\n'; eb.reset(numbers::nine) std::cout << ec << '\n'; +eb.reset(); +eb[2] = true; +eb[numbers::three] = true; +std::cout << eb << '\n'; +std::cout << eb.rotr(1) << '\n'; ``` _output_ ``` @@ -997,6 +1148,8 @@ true true 1000000001 0000000001 +0000001100 +0000011000 ``` ## d) Other functions ### i. `operator bool` @@ -1014,25 +1167,52 @@ _output_ 0001001111 ``` -### ii. `std::ostream& operator<<`, `to_string` +### ii. `operator std::bitset()` +```c++ +constexpr operator std::bitset() const; +``` +Cast an `enum_bitset` to a `std::bitset` with the same number of bits. + +```c++ +enum_bitset ec(numbers::one,numbers::three,numbers::six); +std::bitset<10> bs{ec}; +std::cout << bs << '\n'; +``` +_output_ +```CSV +0001001010 +``` + +### iii. `std::ostream& operator<<`, `to_string`, `to_hex_string` ```c++ friend constexpr std::ostream& operator<<(std::ostream& os, const enum_bitset& what); constexpr std::string to_string(char zero='0', char one='1') const; + +template +constexpr std::string to_hex_string() const; + +constexpr std::string to_hex_string() const; ``` Inserts default string representation into `std::ostream`.
-Returns a `std::string` representation of the bitset. Optionally specify which characters to use for `0` and `1`. +Returns a `std::string` representation of the bitset. Optionally specify which characters to use for `0` and `1`.
+Returns a `std::string` representation of the bitset in hex format. Optionally specify `showbase` which will prefix +the string with `0x` or `0X`; optionally specify `uppercase` which will set the case of the hex digits. ```c++ enum_bitset ec(numbers::one,numbers::three,numbers::six); std::cout << ec << '\n'; std::cout << ec.to_string('-', '+') << '\n'; +std::cout << ec.to_hex_string() << '\n'; +std::cout << ec.to_hex_string() << '\n'; ``` _output_ ```CSV 0001001010 ---+--+-+- +0x4a +0X4A ``` -### iii. `for_each`, `for_each_n` +### iv. `for_each`, `for_each_n` ```c++ template requires std::invocable @@ -1092,7 +1272,7 @@ numbers::two numbers::five ``` -### iv. Using `conjure_enum::dispatch` with `enum_bitset` +### v. Using `conjure_enum::dispatch` with `enum_bitset` Using an `enum_bitset` wth `conjure_enum::dispatch` can be a convenient way of iterating through a set of bits to call specific functions using `for_each`. The following demonstrates this: ```c++ const auto dd3 @@ -1122,16 +1302,52 @@ _output_ not found: 5 ``` +### vi. `get_underlying` +```c++ +constexpr U get_underlying() const; +``` +Returns the underlying integral value. + +### vii. `get_underlying_bit_size` +```c++ +constexpr int get_underlying_bit_size() const +``` +Returns the number of bits that the underlying integral contains. Will always be a power of 2 and an integral type. The number of bits may be larger +than the count of bits. + +### viii. `get_bit_mask`,`get_unused_bit_mask` +```c++ +constexpr U get_bit_mask() const; +constexpr U get_unused_bit_mask() const; +``` +Returns a bit mask that would mask off the _unused_ bits of the underlying integral.
+Returns a bit mask that would mask off the _used_ bits of the underlying integral. + +### ix. `std::hash>` +```c++ +template +struct std::hash>; +``` +Provides a specialization of `std::hash` for `enum_bitset`. + --- -# 5. API and Examples using `conjure_type` +# 5. `conjure_type` `conjure_type` is a general purpose class allowing you to extract a string representation of any typename. The string will be stored statically by the compiler, so you can use the statically generated value `name` to obtain your type. +> [!IMPORTANT] +> You must include +> ```C++ +> #include +> #include +> ``` + +## a) `name` +This static member is generated for your type. It is a `fixed_string` but has a built-in `std::string_view` operator. ```c++ template class conjure_type; static constexpr fixed_string name; ``` -This static member is generated for your type. It is a `fixed_string` but has a built-in `std::string_view` operator. ```c++ class foo; @@ -1185,45 +1401,133 @@ _output_ std::basic_string_view ``` +## b) `as_string_view` +Return the name as a `std::string_view`. +```c++ +static constexpr std::string_view as_string_view(); +``` + +## c) `tpeek` +```c++ +static consteval const char *tpeek(); +``` + +These functions return `std::source_location::current().function_name()` as `const char*` strings for type. +The actual output is implementation dependent. See [Results of `source_location`](reference/source_location.md) for implementation specific `std::source_location` results. + +The following code: +```c++ +std::cout << conjure_type::tpeek() << '\n'; +``` +Generates this output with gcc: +```CSV +static consteval const char* FIX8::conjure_type::tpeek() [with T = test] +``` + +--- +# 6. `fixed_string` +`fixed_string` is a specialisation of `std::array` that provides statics storage for an ASCII zero (asciiz) string. The purpose of this class is to allow the +creation of `constexpr` strings with specfic storage, adding a trailing `0`. It is used by `conjure_enum` to store all strings. API is described below. + +## a) Creating a `fixed_string` +```c++ +template +class fixed_string; +constexpr fixed_string(std::string_view sv); +``` +Constructs a `fixed_string` from a `std::string_view`. The source string is _copied_ and a null character is added to the end. Note the size of the _source_ string must be passed as a template parameter. +```c++ +std::string_view sv{"The rain in Spain"}; +constexpr fixed_string fs{sv}; +``` + +## b) `get` +```c++ +constexpr std::string_view get() const; +``` +Returns the string as a `std::string_view`. + +## c) `c_str` +```c++ +constexpr const char *c_str() const; +``` +Returns the string as a null terminated `const char *`. + +## d) `operator std::string_view` +```c++ +constexpr operator std::string_view() const; +``` +Provides a `std::string_view` cast. Returns the string as a `std::string_view`. + +## e) `operator[]` +```c++ +constexpr char operator[](size_t idx) const; +``` +Returns the character at the specifdined index. It is not range checked. + +## f) `size` +```c++ +constexpr std::size_t size() const; +``` +Returns the size of the `fixed_string` including the null terminator. + +## g) `std::ostream& operator<<` +```c++ +std::ostream& operator<<(std::ostream& os, const fixed_string& what) +``` +Provides an `ostream` insertor. + --- -# 6. Building +# 7. Building This implementation is header only. Apart from standard C++20 includes there are no external dependencies needed in your application. [Catch2](https://github.com/catchorg/Catch2.git) is used for the built-in unit tests. +> [!TIP] +> The unit test source files [unittests.cpp](utests/unittests.cpp) and [edgetests.cpp](utests/edgetests.cpp) contain additional examples for all +> the APIs. ## a) Obtaining the source, building the unittests and examples -### \*nix based environments +### i. Build platform +#### \*nix based environments To clone and default build the test app, unit tests and the benchmark: ```bash -git clone https://github.com/fix8mt/conjure_enum.git -cd conjure_enum -mkdir build -cd build -cmake .. -make -j4 -ctest (or make test) -``` -By default all warnings are enabled. To prevent this, pass the following to cmake: -```cmake -cmake -DBUILD_ALL_WARNINGS=false .. -``` -By default the unit tests are built (which will download Catch2). To prevent this, pass the following to cmake: -```cmake -cmake -DBUILD_UNITTESTS=false .. -``` -To disable stripping of the executables: -```cmake -cmake -DBUILD_STRIP_EXE=false .. -``` -### Windows environments +$ git clone https://github.com/fix8mt/conjure_enum.git +$ cd conjure_enum +$ mkdir build +$ cd build +$ cmake .. +$ make -j4 +$ ctest (or make test) +``` +#### Windows environments Create a new console project. Add the repo `https://github.com/fix8mt/conjure_enum.git` and clone the source. Make sure you set the C++ language to C++20 in the project preferences. The project should build and run the unit tests by default. The package is also available on [vckpg](https://vcpkg.io/en/package/conjure-enum). +### ii. Default compiler warnings +By default all warnings are enabled. To prevent this, pass the following to cmake: +```bash +$ cmake -DBUILD_ALL_WARNINGS=false .. +``` +### iii. Default unit tests +By default the unit tests are built (which will download Catch2). To prevent this, pass the following to cmake: +```bash +$ cmake -DBUILD_UNITTESTS=false .. +``` +### iv. Default executable stripping +To disable stripping of the executables: +```bash +$ cmake -DBUILD_STRIP_EXE=false .. +``` +### v. Clang compilation profiling +To enable clang compilation profiling: +```bash +$ cmake -DBUILD_CLANG_PROFILER=true .. +``` ## b) Using in your application with cmake In `CMakeLists.txt` set your include path to: -```cmake +```CMake include_directories([conjure_enum directory]/include) # e.g. set(cjedir /home/dd/prog/conjure_enum) @@ -1241,7 +1545,7 @@ using namespace FIX8; ## c) Integrating `conjure_enum` in your project with cmake FetchContent You can use cmake [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html) to integrate `conjure_enum` with your project. If your project was called `myproj` with the sourcefile `myproj.cpp` then... -```cmake +```CMake project(myproj) add_executable (myproj myproj.cpp) set_target_properties(myproj PROPERTIES CXX_STANDARD 20 CXX_STANDARD_REQUIRED true) @@ -1255,11 +1559,11 @@ target_include_directories(myproj PRIVATE ${conjure_enum_SOURCE_DIR}/include) ## d) Reporting issues Raise an [issue](https://github.com/fix8mt/conjure_enum/issues) on the github page. The executable `srcloctest` should be built when you build the package by default. This application -does not use any of the `conjure_enum` library and is designed to report on how your compiler handles `std::source_location`. +does not use any of the `conjure_enum` library and is designed to report how your compiler handles `std::source_location`. The actual output is implementation dependent. See [Results of `source_location`](reference/source_location.md) for implementation specific `std::source_location` results. You should attach the output of this application with your issue. > [!TIP] -> Passing the switch `-m` causes `srcloctest` to generate github markdown which you can paste directly into the issue. +> Use the `-m` switch with `srcloctest` to generate github markdown which you can paste directly into the issue. ```C++ $ ./srcloctest @@ -1321,10 +1625,16 @@ Contributions are welcome. Make your changes in [your fork on the dev branch](ht master will not be considered. --- -# 7. Notes +# 8. Notes ## a) enum limits +Compilation times increase with the number of enums that use `conjure_enum` in any compilation unit. +1. For a simple project with few enums, there is probably no need to set any limits; +1. Where the enum is defined elsewhere (say if you are using `std::errc`) then use `enum_range` or one of the convenience macros; +1. Where the enum is unscoped then use `enum_range` or one of the convenience macros; +1. Where you have defined the enum yourself and it is a scoped enum, use `T::ce_first` and `T::ce_last`, or 2. + ### i. `FIX8_CONJURE_ENUM_MIN_VALUE`, `FIX8_CONJURE_ENUM_MAX_VALUE` -These are set by default unless you override them by defining them in your application. They are the global range default for enums using `conjure_enum`. +These are set by default unless you override them by defining them in your application. They are the global range default for all enums using `conjure_enum`. > [!IMPORTANT] > If you want to define these values they must appear _before_ you include `conjure_enum.hpp`. @@ -1347,6 +1657,7 @@ These definitions set the minimum and maximum enum values that are supported. Yo > If you wish to set ranges on a per enum basis, use `enum_range` (see below). ### ii. using `enum_range` +You can specialise this class to override the defaults and set your own range on a per enum basis. ```c++ template struct enum_range @@ -1357,7 +1668,6 @@ struct enum_range The `min` and `max` values are used to set the range of enum values for enums in `conjure_enum`. As shown above, the default values will be `FIX8_CONJURE_ENUM_MIN_VALUE` and `FIX8_CONJURE_ENUM_MAX_VALUE`. -You can specialise this class to override the defaults and set your own range on a per enum basis: ```c++ enum class range_test { first, second, third, fourth, fifth, sixth, seventh, eighth }; template<> @@ -1368,13 +1678,14 @@ struct FIX8::enum_range static_assert(conjure_enum::get_enum_min_value() == 0); static_assert(conjure_enum::get_enum_max_value() == 7); ``` -### iii. `FIX8_CONJURE_ENUM_SET_RANGE_INTS`, `FIX8_CONJURE_ENUM_SET_RANGE` -For convenience, two macros are provided to make it easier to set custom ranges. +#### ii.a `FIX8_CONJURE_ENUM_SET_RANGE_INTS`, `FIX8_CONJURE_ENUM_SET_RANGE` +For convenience, two macros are provided to make it easier to set custom ranges using `enum_range`. ```c++ #define FIX8_CONJURE_ENUM_SET_RANGE_INTS(ec,min_int,max_int)... #define FIX8_CONJURE_ENUM_SET_RANGE(min_enum,max_enum)... ``` The first macro takes an enum typename followed by a lower and upper int range value. + The second macro takes a lower and upper enum value. For example: ```c++ FIX8_CONJURE_ENUM_SET_RANGE_INTS(numbers, 0, 7) @@ -1383,6 +1694,32 @@ FIX8_CONJURE_ENUM_SET_RANGE_INTS(numbers, 0, 7) FIX8_CONJURE_ENUM_SET_RANGE(numbers::first, numbers::eighth) ``` +### iii. using `T::ce_first` and `T::ce_last` +Another approach to setting a custom range for an enum is to alias the first and last enum values in your enum definition +using `ce_first` and `ce_last`. `conjure_enum` will use these values to set the enum range. +You can set either, both or neither. A range value not set will default to the `FIX8_CONJURE_ENUM_MIN_VALUE` or `FIX8_CONJURE_ENUM_MAX_VALUE`. + +> [!WARNING] +> With _unscoped_ enums, there can only be one enum type with `ce_first` and `ce_last` defined in any translation unit (ODR). +> It is therefore recommended to only use this feature with scoped enums. + +For example: +```c++ +enum class range_test +{ + first, second, third, fourth, fifth, sixth, seventh, eighth, + ce_first=first, ce_last=eighth +}; +using rt = conjure_enum; +std::cout << rt::get_enum_min_value() << '/' << rt::get_enum_max_value() << '\n'; +std::cout << rt::get_actual_enum_min_value() << '/' << rt::get_actual_enum_max_value() << '\n'; +``` +_output_ +```CSV +0/7 +0/7 +``` + ## b) Choosing the minimal build ```c++ #define FIX8_CONJURE_ENUM_MINIMAL @@ -1395,23 +1732,56 @@ Static structures and API calls that will be excluded are: scoped_entries unscoped_entries rev_scoped_entries +names unscoped_names remove_scope add_scope unscoped_string_to_enum +for_each,for_each_n +type_name +dispatch +iterators enum_to_string //noscope option not available ``` These are marked ![](assets/notminimalred.svg) in the API documentation above. -## c) Class `conjure_enum` is not constructible +## c) Continuous enum optimization +```c++ +#define FIX8_CONJURE_ENUM_IS_CONTINUOUS +``` +If your enum(s) are continuous (no gaps) you can enable this compiler optimization +by defining `FIX8_CONJURE_ENUM_IS_CONTINUOUS` _before_ you include `conjure_enum.hpp`. +Our testing shows a reduction in overall compile times. All enums using `conjure_enum.hpp` in the current compilation unit must be continuous. + +## d) Anonymous enum optimization +```c++ +#define FIX8_CONJURE_ENUM_NO_ANON +``` +If your enum(s) are not within any anonymous namespaces (rarely used for this purpose), you can enable this compiler optimization +by defining `FIX8_CONJURE_ENUM_NO_ANON` _before_ you include `conjure_enum.hpp`. +Our testing shows a reduction in overall compile times. All enums using `conjure_enum.hpp` in the current compilation unit must be continuous. + +## e) Enable all optimizations +```c++ +#define FIX8_CONJURE_ENUM_ALL_OPTIMIZATIONS +``` +You can enable all optimizations described above by defining `FIX8_CONJURE_ENUM_ALL_OPTIMIZATIONS` _before_ you include `conjure_enum.hpp`. +This is the equivalent of defining: +```c++ +#define FIX8_CONJURE_ENUM_MINIMAL +#define FIX8_CONJURE_ENUM_IS_CONTINUOUS +#define FIX8_CONJURE_ENUM_NO_ANON +``` + +## f) Class `conjure_enum` is not constructible All methods in this class are _static_. You cannot instantiate an object of this type. The same goes for `conjure_type`. -## d) It's not _real_ reflection +## g) It's not _real_ reflection This library provides a workaround (hack :smirk:) to current limitations of C++. There are proposals out there for future versions of the language that will provide proper reflection. See [Reflection TS](https://en.cppreference.com/w/cpp/experimental/reflect) and [Reflection for C++26](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2996r0.html) for examples of some of these. -## e) Use of `std::string_view` +## h) Use of `std::string_view` All of the generated static strings and generated static tables obtained by `std::source_location` use the library defined `fixed_string`. No string copying is done at runtime, resulting in a single static string in your application. All `conjure_enum` methods that return strings _only_ return `std::string_view`. To demonstrate this, lets look at the supplied test application `statictest`: @@ -1425,8 +1795,6 @@ int main(void) { for(const auto& [a, b] : conjure_enum::entries) std::cout << conjure_enum::enum_to_int(a) << ' ' << b << '\n'; - for(const auto& a : conjure_enum::names) - std::cout << a << '\n'; std::cout << static_cast(conjure_enum::string_to_enum("component::path").value()) << '\n'; std::cout << conjure_enum::get_enum_min_value() << '/' << conjure_enum::get_enum_max_value() << '\n'; return 0; @@ -1534,35 +1902,156 @@ $ It can be observed that there is only _one_ copy of the scoped enum value string in the executable. -## f) Clang compile profiling -You can profile the compile time for Clang (other compilers TBA). Firstly install [ClangBuildAnalyzer](https://github.com/aras-p/ClangBuildAnalyzer). Then configure with: +## i) Compilation profiling (Clang) +You can profile the compile time for Clang (other compilers TBA). Firstly install [ClangBuildAnalyzer](https://github.com/aras-p/ClangBuildAnalyzer). +Then configure `conjure_enum` with: ```CMake cmake -DBUILD_CLANG_PROFILER=true .. ``` -You can follow the instructions given on the `ClangBuildAnalyzer` page to run, alternatively after you build the included program `cbenchmark.cpp`, +You can follow the instructions given on the `ClangBuildAnalyzer` page to run. Alternatively after you build the included program `cbenchmark`, run the included script [cbenchmark.sh](examples/cbenchmark.sh). The script expects the following environment variables: | Variable | Description | | :--- | :--- | | `ClangBuildAnalyzerLoc` | directory where ClangBuildAnalyzer can be found| -| `ArtifactLoc` | directory where conjure_enum is built| +| `ArtifactLoc` | directory where `conjure_enum` is built| -For example, if `ClangBuildAnalyzer` was built in `~/prog/ClangBuildAnalyzer/build` and your `conjure_enum` build was in `./build_clang`, then: +For example, if `ClangBuildAnalyzer` was built in `~/prog/ClangBuildAnalyzer/build` and your `conjure_enum` build was in `./build_clang`, then you +would run the script from the `conjure_enum` directory as follows: ```bash ClangBuildAnalyzerLoc=~/prog/ClangBuildAnalyzer/build ArtifactLoc=build_clang examples/cbenchmark.sh ``` -The results will be printed to the screen. +The results will be printed to the screen. For example: +
output +

+ +```CSV +Processing all files and saving to 'cbenchmark.dat'... + done in 0.0s. Run 'ClangBuildAnalyzer --analyze cbenchmark.dat' to analyze it. +Analyzing build trace from 'cbenchmark.dat'... +**** Time summary: +Compilation (2 times): + Parsing (frontend): 0.4 s + Codegen & opts (backend): 0.0 s + +**** Files that took longest to parse (compiler frontend): + 423 ms: build_clang/CMakeFiles/cbenchmark.dir/examples/cbenchmark.cpp.o + +**** Templates that took longest to instantiate: + 187 ms: FIX8::conjure_enum (1 times, avg 187 ms) + 119 ms: FIX8::conjure_enum::_entries<0UL, 1UL, 2UL, 3UL, 4UL, 5UL... (1 times, avg 119 ms) + 8 ms: FIX8::conjure_enum::_sorted_entries (1 times, avg 8 ms) + 6 ms: std::sort> *, boo... (1 times, avg 6 ms) + 6 ms: std::__sort> *, _... (1 times, avg 6 ms) + 5 ms: std::__introsort_loop>, 72> (1 times, avg 3 ms) + 2 ms: std::tuple, char, std::basic_string_vie... (1 times, avg 2 ms) + 2 ms: std::__unguarded_partition_pivot> *... (1 times, avg 2 ms) + 2 ms: std::__partial_sort> (1 times, avg 2 ms) + 2 ms: std::__heap_select>... (1 times, avg 2 ms) + 1 ms: std::optional (1 times, avg 1 ms) + 1 ms: std::to_array, char, std::ba... (1 times, avg 1 ms) + 1 ms: std::basic_string (1 times, avg 1 ms) + 1 ms: std::basic_string (1 times, avg 1 ms) + 1 ms: std::__adjust_heap (1 times, avg 1 ms) + 1 ms: std::basic_string (1 times, avg 1 ms) + 1 ms: std::basic_string (1 times, avg 1 ms) + 1 ms: std::__and_, std::__is_swappable, char, std::basic_s... (1 times, avg 1 ms) + 1 ms: std::__final_insertion_sort::_M_construct (1 times, avg 1 ms) + 1 ms: std::basic_string::_M_construct (1 times, avg 1 ms) + 1 ms: std::basic_string::_M_construct (1 times, avg 1 ms) + 1 ms: std::operator+, std::allocator> (1 times, avg 1 ms) + +**** Template sets that took longest to instantiate: + 187 ms: FIX8::conjure_enum<$> (1 times, avg 187 ms) + 119 ms: FIX8::conjure_enum<$>::_entries<$> (1 times, avg 119 ms) + 49 ms: FIX8::conjure_enum<$>::_get_name<$> (72 times, avg 0 ms) + 12 ms: std::tuple<$>::tuple<$> (21 times, avg 0 ms) + 8 ms: FIX8::conjure_enum<$>::_sorted_entries (1 times, avg 8 ms) + 7 ms: std::basic_string<$> (5 times, avg 1 ms) + 6 ms: std::sort<$> (1 times, avg 6 ms) + 6 ms: std::__sort<$> (1 times, avg 6 ms) + 5 ms: std::basic_string<$>::_M_construct<$> (5 times, avg 1 ms) + 5 ms: std::tuple<$> (2 times, avg 2 ms) + 5 ms: std::__introsort_loop<$> (1 times, avg 5 ms) + 4 ms: std::array<$> (2 times, avg 2 ms) + 2 ms: std::__unguarded_partition_pivot<$> (1 times, avg 2 ms) + 2 ms: std::__and_<$> (3 times, avg 0 ms) + 2 ms: std::__move_median_to_first<$> (1 times, avg 2 ms) + 2 ms: std::iter_swap<$> (1 times, avg 2 ms) + 2 ms: std::__partial_sort<$> (1 times, avg 2 ms) + 2 ms: std::__heap_select<$> (1 times, avg 2 ms) + 2 ms: std::__make_heap<$> (1 times, avg 2 ms) + 1 ms: std::_Tuple_impl<$> (2 times, avg 0 ms) + 1 ms: std::optional<$> (1 times, avg 1 ms) + 1 ms: std::to_array<$> (1 times, avg 1 ms) + 1 ms: std::__adjust_heap<$> (1 times, avg 1 ms) + 1 ms: std::basic_string<$>::basic_string (2 times, avg 0 ms) + 1 ms: std::__final_insertion_sort<$> (1 times, avg 1 ms) + 1 ms: std::operator+, std::allocator> (1 times, avg 1 ms) + 1 ms: std::__insertion_sort<$> (1 times, avg 1 ms) + 0 ms: FIX8::conjure_enum<$>::_tuple_comp_rev (1 times, avg 0 ms) + 0 ms: std::tuple>::operator= (1 times, avg 0 ms) + 0 ms: __gnu_cxx::__to_xstring<$> (1 times, avg 0 ms) + +**** Functions that took longest to compile: + 0 3s: test_conjure_enum(std::errc) (/home/davidd/prog/conjure_enum_tclass/examples/cbenchmark.cpp) + +**** Function sets that took longest to compile / optimize: + +**** Expensive headers: +166 ms: /usr/include/c++/14/system_error (included 1 times, avg 166 ms), included via: + 1x: + +54 ms: /home/davidd/prog/conjure_enum_tclass/include/fix8/conjure_enum.hpp (included 1 times, avg 54 ms), included via: + 1x: + + done in 0.0s. +``` + +

+ +--- +# 9. Benchmarks +We have benchmarked compilation times for `conjure_enum` and `magic_enum`. +For `magic_enum` we created a separate repo (see [here](https://github.com/fix8mt/magic_enum_benchmark)). + +| Compiler | `conjure_enum` (secs) | `magic_enum` (secs)| Notes | +| :--- | :--- | :--- |:--- | +| **MSVC** | 0.376 | 0.343 | using cl from command prompt| +|_command_ | `cl /nologo /MD /std:c++latest /Bt+ /I ..\include ..\examples\cbenchmark.cpp\|find "c1xx.dll"` | `cl /nologo /MD /std:c++latest /Bt+ -I build\_deps\magic_enum-src\include cbenchmark.cpp\|find "c1xx.dll"`|| +| **clang** | 0.3 | 0.3 | using ClangBuildAnalyzer| +|_command_|`make`; `ClangBuildAnalyzerLoc=~/prog/ClangBuildAnalyzer/build ArtifactLoc=build_clang examples/cbenchmark.sh`|`make`; `ClangBuildAnalyzerLoc=~/prog/ClangBuildAnalyzer/build ArtifactLoc=build_clang ./cbenchmark.sh`|| + +## Notes +- Benchmark run 10 times, best result shown +- Both benchmarks are using [cbenchmark.sh](examples/cbenchmark.sh) and [cbenchmark.cpp](examples/cbenchmark.cpp) +- MSVC: Windows 11 ThinkCentre 16x 13th Gen Intel i7-13700, 32Gb; MSVC 2022 / 17.11.0. +- Clang: Ubuntu 24.04 12th Gen Intel i9-12900T, 32Gb; Clang 18.1.3 +- `magic_enum`: single header only +- `conjure_enum`: minimal build + +## Discussion +For MSVC, `magic_enum` compilation times a slighly better than `conjure_enum` (around %9). For clang the results are identical. +From a compilation performance perspective, `conjure_enum` roughly matches the performance of `magic_enum`. --- -# 8. Compiler support +# 10. Compiler support | Compiler | Version(s) | Notes | Unsupported | | :--- | :--- | :--- | ---: | | [gcc](https://gcc.gnu.org/projects/cxx-status.html) | `11`, `12`, `13`, `14`| `std::format` not complete in `11`, `12` | `<= 10` | | [clang](https://clang.llvm.org/cxx_status.html) | `15`, `16`, `17`, `18`| Catch2 needs `cxx_std_20` in `15` | `<= 14` | -| [msvc](https://learn.microsoft.com/en-us/cpp/overview/visual-cpp-language-conformance) | `16`, `17` | Visual Studio 2019,2022, latest `17.10.5`| `<= 16.9`| -| [xcode](https://developer.apple.com/support/xcode/) | `15` | Apple LLVM 15.0.0, some issues with `constexpr`, workarounds| `<= 14`| +| [msvc](https://learn.microsoft.com/en-us/cpp/overview/visual-cpp-language-conformance) | `16`, `17` | Visual Studio 2019,2022, latest `17.11.2`| `<= 16.9`| +| [xcode](https://developer.apple.com/support/xcode/) | `15` | Apple Xcode Clang 15.0.0 (LLVM 16), some issues with `constexpr`, workarounds| `<= 14`| -# 9. Compiler issues +# 11. Compiler issues | Compiler | Version(s) | Issues | Workaround | | :--- | :--- | :--- | ---: | | clang | `16`, `17`, `18`| Compiler reports integers outside valid range [x,y]| specify underlying type when declaring enum eg. `enum class foo : int` | diff --git a/examples/cbenchmark.cpp b/examples/cbenchmark.cpp index 31620db9..d396bcf8 100644 --- a/examples/cbenchmark.cpp +++ b/examples/cbenchmark.cpp @@ -28,16 +28,35 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. //---------------------------------------------------------------------------------------- -#include -#define FIX8_CONJURE_ENUM_MINIMAL +// CLI msvc build benchmark from your build dir +// call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" +// cl /nologo /MD /std:c++latest /Bt+ /I ..\include ..\examples\cbenchmark.cpp|find "c1xx.dll" +//---------------------------------------------------------------------------------------- +#define FIX8_CONJURE_ENUM_ALL_OPTIMIZATIONS #include //----------------------------------------------------------------------------------------- -FIX8_CONJURE_ENUM_SET_RANGE_INTS(std::errc, 0, 71) +enum class numbers +{ + zero, one, two, three, four, + five, six, seven, eight, nine, + ten, eleven, twelve, thirteen, fourteen, + fifteen, sixteen, seventeen, eighteen, nineteen, + twenty, twenty_one, twenty_two, twenty_three, twenty_four, + twenty_five, twenty_six, twenty_seven, twenty_eight, twenty_nine, + thirty, thirty_one, thirty_two, thirty_three, thirty_four, + thirty_five, thirty_six, thirty_seven, thirty_eight, thirty_nine, + forty, forty_one, forty_two, forty_three, forty_four, + forty_five, forty_six, forty_seven, forty_eight, forty_nine, + fifty, fifty_one, fifty_two, fifty_three, fifty_four, + fifty_five, fifty_six, fifty_seven, fifty_eight, fifty_nine, + sixty, sixty_one, sixty_two, sixty_three +}; +FIX8_CONJURE_ENUM_SET_RANGE(numbers::zero, numbers::sixty_three); -int test_conjure_enum(std::errc err) +auto test_conjure_enum(numbers num) { - return FIX8::conjure_enum::enum_to_string(err).size(); + return FIX8::conjure_enum::enum_to_string(num).size(); } int main(void) diff --git a/examples/example.cpp b/examples/example.cpp index 9cf0ed2b..ec8188de 100644 --- a/examples/example.cpp +++ b/examples/example.cpp @@ -40,6 +40,8 @@ #endif #include +#include +#include //----------------------------------------------------------------------------------------- using namespace std::literals::string_view_literals; @@ -52,6 +54,24 @@ enum component1 : int { scheme, authority, userinfo, user, password, host, port, enum class numbers : int { zero, one, two, three, four, five, six, seven, eight, nine }; enum class numbers1 : int { zero1=4, one1=3, two1=2, three1, four1, five1, six1, seven1, eight1, nine1 }; +//----------------------------------------------------------------------------------------- +struct foo1 +{ + int process(component val, int aint) const + { + return aint * static_cast(val); + } + static constexpr auto dd2a + { + std::to_array> + ({ + { component::scheme, &foo1::process }, + { component::port, &foo1::process }, + { component::fragment, &foo1::process }, + }) + }; +}; + //----------------------------------------------------------------------------------------- template const std::string demangle() noexcept @@ -71,6 +91,7 @@ const std::string demangle() noexcept return typeid(T).name(); } +//----------------------------------------------------------------------------------------- int main(void) { conjure_enum::for_each_n(3, [](component val, int other) { std::cout << static_cast(val) << ' ' << other << '\n'; }, 200); @@ -226,5 +247,15 @@ int main(void) std::cout << '"' << component::host << '"' << '\n'; std::cout << '"' << component1::host << '"' << '\n'; std::cout << '"' << static_cast(100) << '"' << '\n'; + + std::cout << std::hash>{}(er) << '\n'; + + foo1 bar1; + std::cout << conjure_enum::dispatch(component::port, -1, foo1::dd2a, &bar1, 1000) << '\n'; + + using en = conjure_enum; + for (const auto& [ev,str] : {en::front(), en::back()}) + std::cout << static_cast(ev) << ' ' << str << '\n'; + return 0; } diff --git a/examples/srcloctest.cpp b/examples/srcloctest.cpp index 566831ec..efee3dd0 100644 --- a/examples/srcloctest.cpp +++ b/examples/srcloctest.cpp @@ -152,9 +152,9 @@ int main(int argc, char **argv) }; bool mkd{}, cpl{true}, hlp{}; - std::map opts { {"-m",mkd},{"-c",cpl},{"-h",hlp} }; - for (int ii{1}; ii < argc; ++ii) - if (auto result{opts.find(std::string_view(argv[ii]))}; result != opts.cend()) + const std::map opts { {"-m",mkd},{"-c",cpl},{"-h",hlp} }; + for (const std::vector args{argv + 1, argv + argc}; const auto pp : args) + if (auto result{opts.find(pp)}; result != opts.cend()) result->second ^= true; if (hlp) { diff --git a/examples/statictest.cpp b/examples/statictest.cpp index e3afa6b6..7956a1f0 100644 --- a/examples/statictest.cpp +++ b/examples/statictest.cpp @@ -44,8 +44,6 @@ int main(void) { for(const auto& [a, b] : conjure_enum::entries) std::cout << conjure_enum::enum_to_int(a) << ' ' << b << '\n'; - for(const auto& a : conjure_enum::names) - std::cout << a << '\n'; std::cout << static_cast(conjure_enum::string_to_enum("component::path").value()) << '\n'; std::cout << conjure_enum::get_enum_min_value() << '/' << conjure_enum::get_enum_max_value() << '\n'; return 0; diff --git a/include/fix8/conjure_enum.hpp b/include/fix8/conjure_enum.hpp index eb974679..264c24b4 100644 --- a/include/fix8/conjure_enum.hpp +++ b/include/fix8/conjure_enum.hpp @@ -31,6 +31,8 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. //---------------------------------------------------------------------------------------- +// conjure_enum +//---------------------------------------------------------------------------------------- #ifndef FIX8_CONJURE_ENUM_HPP_ #define FIX8_CONJURE_ENUM_HPP_ @@ -39,19 +41,29 @@ #endif //---------------------------------------------------------------------------------------- +#if defined FIX8_CONJURE_ENUM_ALL_OPTIMIZATIONS +# if not defined FIX8_CONJURE_ENUM_IS_CONTINUOUS +# define FIX8_CONJURE_ENUM_IS_CONTINUOUS +# endif +# if not defined FIX8_CONJURE_ENUM_NO_ANON +# define FIX8_CONJURE_ENUM_NO_ANON +# endif +# if not defined FIX8_CONJURE_ENUM_MINIMAL +# define FIX8_CONJURE_ENUM_MINIMAL +# endif +#endif + +//----------------------------------------------------------------------------------------- #include #include #include #include #include #include -#include -#include -#include -#include -#include -#include #include +#if not defined FIX8_CONJURE_ENUM_MINIMAL +# include +#endif //----------------------------------------------------------------------------------------- namespace FIX8 { @@ -70,14 +82,15 @@ namespace FIX8 { template class fixed_string final { - std::array _buff; - template - constexpr fixed_string(std::string_view sv, std::integer_sequence) noexcept : _buff{sv[C]..., 0} {} + const std::array _buff; + template + constexpr fixed_string(std::string_view sv, std::index_sequence) noexcept : _buff{sv[I]..., 0} {} public: - explicit constexpr fixed_string(std::string_view sv) noexcept : fixed_string{sv.data(), std::make_integer_sequence{}} {} + explicit constexpr fixed_string(std::string_view sv) noexcept : fixed_string{sv, std::make_index_sequence{}} {} constexpr fixed_string() = delete; constexpr std::string_view get() const noexcept { return { _buff.data(), N }; } + constexpr const char *c_str() const noexcept { return _buff.data(); } constexpr operator std::string_view() const noexcept { return get(); } constexpr char operator[](size_t idx) const noexcept { return _buff[idx]; } constexpr std::size_t size() const noexcept { return _buff.size(); } @@ -103,7 +116,7 @@ class static_only //----------------------------------------------------------------------------------------- // compiler specifics //----------------------------------------------------------------------------------------- -class cs : public static_only +class cs final : public static_only { static constexpr auto _specifics { @@ -128,10 +141,7 @@ class cs : public static_only enum class sval { start, end, anon_str, anon_start }; template // can't have constexpr decompositions! (but why not?) - static constexpr auto get_spec() noexcept - { - return std::get(N)>(_specifics[static_cast(V)]); - } + static constexpr auto get_spec() noexcept { return std::get(N)>(_specifics[static_cast(V)]); } static constexpr auto size() noexcept { return sizeof(_specifics); } }; using stype = cs::stype; @@ -153,18 +163,28 @@ concept valid_enum = requires(T) //----------------------------------------------------------------------------------------- // You can specialise this class to define a custom range for your enum +// Alternatively, alias T::ce_first as the first and T::ce_last as the last enum in +// your enum declaration //----------------------------------------------------------------------------------------- -template -struct enum_range : public static_only +template +class enum_range final : public static_only { - static constexpr int min{FIX8_CONJURE_ENUM_MIN_VALUE}, max{FIX8_CONJURE_ENUM_MAX_VALUE}; + static constexpr int get_first() noexcept requires std::is_enum_v + { return static_cast(T::ce_first); } + static constexpr int get_last() noexcept requires std::is_enum_v + { return static_cast(T::ce_last); } + static constexpr int get_first() noexcept { return FIX8_CONJURE_ENUM_MIN_VALUE; } + static constexpr int get_last() noexcept { return FIX8_CONJURE_ENUM_MAX_VALUE; }; + +public: + static constexpr int min{get_first()}, max{get_last()}; }; //----------------------------------------------------------------------------------------- // Convenience macros for above //----------------------------------------------------------------------------------------- #define FIX8_CONJURE_ENUM_SET_RANGE_INTS(ec,minv,maxv) \ - template<> struct FIX8::enum_range { static constexpr int min{minv}, max{maxv}; }; + template<> struct FIX8::enum_range final { static constexpr int min{minv}, max{maxv}; }; #define FIX8_CONJURE_ENUM_SET_RANGE(minv,maxv) \ FIX8_CONJURE_ENUM_SET_RANGE_INTS(decltype(minv),static_cast(minv), static_cast(maxv)) @@ -174,102 +194,43 @@ template class conjure_enum : public static_only { static constexpr int enum_min_value{enum_range::min}, enum_max_value{enum_range::max}; - static_assert(enum_max_value > enum_min_value, "FIX8_CONJURE_ENUM_MAX_VALUE must be greater than FIX8_CONJURE_ENUM_MIN_VALUE"); + static_assert(enum_max_value > enum_min_value, + "FIX8_CONJURE_ENUM_MAX_VALUE, enum_range::max or T::ce_last must be greater than FIX8_CONJURE_ENUM_MIN_VALUE, enum_range::min or T::ce_first) "); public: using enum_tuple = std::tuple; using scoped_tuple = std::tuple; -private: - template - static constexpr auto _enum_name() noexcept - { - constexpr auto result { _get_name() }; - return fixed_string(result); - } + static consteval const char *tpeek() noexcept { return std::source_location::current().function_name(); } template - static constexpr auto _enum_name_v { _enum_name() }; - - template - static constexpr auto _entries(std::index_sequence) noexcept - { - return std::array{{{ values[I], _enum_name_v}...}}; - } - - template - static constexpr auto _names(std::index_sequence) noexcept - { - return std::array{{{ _enum_name_v}...}}; - } - - static constexpr auto _sorted_entries() noexcept - { - auto tmp { entries }; - std::sort(tmp.begin(), tmp.end(), _tuple_comp_rev); - return tmp; - } - -#if not defined FIX8_CONJURE_ENUM_MINIMAL - template - static constexpr auto _unscoped_entries(std::index_sequence) noexcept - { - std::array tmp{{{ values[I], _remove_scope(_enum_name_v)}...}}; - std::sort(tmp.begin(), tmp.end(), _tuple_comp_rev); - return tmp; - } - - static constexpr std::string_view _remove_scope(std::string_view what) noexcept - { - if (const auto lc { what.find_last_of(':') }; lc != std::string_view::npos) - return what.substr(lc + 1); - return what; - } - - template - static constexpr auto _scoped_entries(std::index_sequence) noexcept - { - std::array tmp{{{ _remove_scope(_enum_name_v), _enum_name_v}...}}; - std::sort(tmp.begin(), tmp.end(), _scoped_comp); - return tmp; - } - - template - static constexpr auto _rev_scoped_entries(std::index_sequence) noexcept - { - std::array tmp{{{ _enum_name_v, _remove_scope(_enum_name_v)}...}}; - std::sort(tmp.begin(), tmp.end(), _scoped_comp); - return tmp; - } + static consteval const char *epeek() noexcept { return std::source_location::current().function_name(); } - template - static constexpr auto _unscoped_names(std::index_sequence) noexcept - { - return std::array{{{ _remove_scope(_enum_name_v)}...}}; - } -#endif +private: + template + static constexpr std::string_view _epeek_v { epeek() }; template static constexpr bool _is_valid() noexcept { - constexpr std::string_view from{epeek()}; - if constexpr (constexpr auto ep { from.rfind(cs::get_spec()) }; ep == std::string_view::npos) + if constexpr (constexpr auto ep { _epeek_v.rfind(cs::get_spec()) }; ep == std::string_view::npos) return false; #if defined __clang__ - else if constexpr (from[ep + cs::get_spec().size()] == '(') + else if constexpr (_epeek_v[ep + cs::get_spec().size()] == '(') { - if constexpr (from[ep + cs::get_spec().size() + 1] == '(') + if constexpr (_epeek_v[ep + cs::get_spec().size() + 1] == '(') return false; - if constexpr (constexpr auto lstr { from.substr(ep + cs::get_spec().size()) }; - lstr.find(cs::get_spec()) != std::string_view::npos) // is anon - return true; +#if not defined FIX8_CONJURE_ENUM_NO_ANON + if constexpr (_epeek_v.find(cs::get_spec(), ep + cs::get_spec().size()) != std::string_view::npos) // is anon + return true; +#endif } - else if constexpr (from.substr(ep + cs::get_spec().size()).find_first_of(cs::get_spec()) != std::string_view::npos) + else if constexpr (_epeek_v.find_first_of(cs::get_spec(), ep + cs::get_spec().size()) != std::string_view::npos) return true; return false; #else - else if constexpr (from[ep + cs::get_spec().size()] != '(' - && from.substr(ep + cs::get_spec().size()).find_first_of(cs::get_spec()) != std::string_view::npos) + else if constexpr (_epeek_v[ep + cs::get_spec().size()] != '(' + && _epeek_v.find_first_of(cs::get_spec(), ep + cs::get_spec().size()) != std::string_view::npos) return true; else return false; @@ -279,6 +240,10 @@ class conjure_enum : public static_only template static constexpr auto _values(std::index_sequence) noexcept { +#if defined FIX8_CONJURE_ENUM_IS_CONTINUOUS + static_assert(sizeof...(I) > 0, "conjure_enum requires non-empty enum"); + return std::array{static_cast(enum_min_value + I)... }; +#else constexpr std::array valid { _is_valid(enum_min_value + I)>()... }; constexpr auto valid_cnt { std::count_if(valid.cbegin(), valid.cend(), [](bool val) noexcept { return val; }) }; static_assert(valid_cnt > 0, "conjure_enum requires non-empty enum"); @@ -287,107 +252,77 @@ class conjure_enum : public static_only if (valid[idx]) vals[nn++] = static_cast(enum_min_value + idx); return vals; +#endif } template static constexpr std::string_view _get_name() noexcept { - constexpr std::string_view from{epeek()}; - constexpr auto ep { from.rfind(cs::get_spec()) }; + constexpr auto ep { _epeek_v.rfind(cs::get_spec()) }; if constexpr (ep == std::string_view::npos) return {}; - if constexpr (from[ep + cs::get_spec().size()] == cs::get_spec()) +#if not defined FIX8_CONJURE_ENUM_NO_ANON + if constexpr (_epeek_v[ep + cs::get_spec().size()] == cs::get_spec()) { #if defined __clang__ - if constexpr (from[ep + cs::get_spec().size() + 1] == cs::get_spec()) + if constexpr (_epeek_v[ep + cs::get_spec().size() + 1] == cs::get_spec()) return {}; #endif - if (constexpr auto lstr { from.substr(ep + cs::get_spec().size()) }; + if (constexpr auto lstr { _epeek_v.substr(ep + cs::get_spec().size()) }; lstr.find(cs::get_spec()) != std::string_view::npos) // is anon if constexpr (constexpr auto lc { lstr.find_first_of(cs::get_spec()) }; lc != std::string_view::npos) return lstr.substr(cs::get_spec().size() + 2, lc - (cs::get_spec().size() + 2)); // eat "::" } - constexpr std::string_view result { from.substr(ep + cs::get_spec().size()) }; +#endif + constexpr std::string_view result { _epeek_v.substr(ep + cs::get_spec().size()) }; if constexpr (constexpr auto lc { result.find_first_of(cs::get_spec()) }; lc != std::string_view::npos) return result.substr(0, lc); else return {}; } -#if not defined FIX8_CONJURE_ENUM_MINIMAL - static constexpr std::string_view _process_scope([[maybe_unused]] const auto& entr, std::string_view what) noexcept + template + static constexpr auto _get_name_v { _get_name() }; + + template + static constexpr auto _enum_name_v { fixed_string<_get_name_v.size()>(_get_name_v) }; + + template + static constexpr auto _entries(std::index_sequence) noexcept { - if constexpr (is_scoped()) - if (const auto result { std::equal_range(entr.cbegin(), - entr.cend(), scoped_tuple(what, std::string_view()), _scoped_comp) }; - result.first != result.second) - return std::get<1>(*result.first); - return what; + return std::array{{{ values[I], _enum_name_v}...}}; + } + + static constexpr auto _sorted_entries() noexcept + { + auto tmp { entries }; + std::sort(tmp.begin(), tmp.end(), _tuple_comp_rev); + return tmp; } -#endif /// comparators static constexpr bool _value_comp(const T& pl, const T& pr) noexcept { - return static_cast(pl) < static_cast(pr); + return pl < pr; } static constexpr bool _tuple_comp(const enum_tuple& pl, const enum_tuple& pr) noexcept { - return static_cast(std::get(pl)) < static_cast(std::get(pr)); + return std::get(pl) < std::get(pr); } static constexpr bool _tuple_comp_rev(const enum_tuple& pl, const enum_tuple& pr) noexcept { return std::get(pl) < std::get(pr); } -#if not defined FIX8_CONJURE_ENUM_MINIMAL - static constexpr bool _scoped_comp(const scoped_tuple& pl, const scoped_tuple& pr) noexcept - { - return std::get<0>(pl) < std::get<0>(pr); - } -#endif public: - static consteval const char *tpeek() noexcept { return std::source_location::current().function_name(); } - - template - static consteval const char *epeek() noexcept { return std::source_location::current().function_name(); } - - static constexpr std::string_view type_name() noexcept - { - constexpr std::string_view from{tpeek()}; -#if defined _MSC_VER - constexpr auto ep { from.rfind(cs::get_spec()) }; - if constexpr (ep == std::string_view::npos) - return {}; - if constexpr (constexpr auto lc { from.find_first_of(cs::get_spec()) }; lc != std::string_view::npos) - { - constexpr auto e1 { from.substr(lc + 1, ep - lc - 2) }; - CHKMSSTR(e1,type_t); - CHKMSSTR(e1,extype_t1); - } - else - return {}; -#else - if constexpr (constexpr auto ep { from.rfind(cs::get_spec()) }; ep != std::string_view::npos) - { - constexpr auto result { from.substr(ep + cs::get_spec().size()) }; - if constexpr (constexpr auto lc { result.find_first_of(cs::get_spec()) }; lc != std::string_view::npos) - return result.substr(0, lc); - } - else - return {}; -#endif - } - struct is_scoped : std::bool_constant>; }>{}; - template - static constexpr bool is_valid() noexcept { return _is_valid(); } - static constexpr auto count() noexcept { return values.size(); } + static constexpr bool is_continuous() noexcept { return (static_cast(max_v) - static_cast(min_v) + 1) == count(); } + static constexpr bool in_range(T value) noexcept { return std::clamp(value, min_v, max_v) == value; } // scope ops static constexpr bool has_scope(std::string_view what) noexcept @@ -397,25 +332,6 @@ class conjure_enum : public static_only else return false; } -#if not defined FIX8_CONJURE_ENUM_MINIMAL - static constexpr std::string_view remove_scope(std::string_view what) noexcept - { - return _process_scope(rev_scoped_entries, what); - } - - static constexpr std::string_view add_scope(std::string_view what) noexcept - { - return _process_scope(scoped_entries, what); - } -#endif - - // iterators - static constexpr auto cbegin() noexcept { return entries.cbegin(); } - static constexpr auto cend() noexcept { return entries.cend(); } - static constexpr auto crbegin() noexcept { return entries.crbegin(); } - static constexpr auto crend() noexcept { return entries.crend(); } - static constexpr auto front() noexcept { return *cbegin(); } - static constexpr auto back() noexcept { return *std::prev(cend()); } // enum <==> int static constexpr int enum_to_int(T value) noexcept @@ -428,494 +344,97 @@ class conjure_enum : public static_only } static constexpr std::optional int_to_enum(int value) noexcept { - if (const auto result { std::equal_range(values.cbegin(), values.cend(), static_cast(value), _value_comp) }; - result.first != result.second) - return *result.first; - return {}; + return contains(enum_cast(value)) ? enum_cast(value) : std::optional{}; + } + static constexpr T enum_cast(int value) noexcept + { + return static_cast(value); } + // index + static constexpr std::optional index(T value) noexcept + { + if constexpr (is_continuous()) + return in_range(value) ? enum_to_underlying(value) - enum_to_underlying(min_v) : std::optional{}; + else + { + const auto [begin,end] { std::equal_range(entries.cbegin(), entries.cend(), enum_tuple(value, std::string_view()), _tuple_comp) }; + return begin != end ? &*begin - &*entries.cbegin() : std::optional{}; + } + } + template + static constexpr std::optional index() noexcept { return index(e); } + // contains + template + static constexpr bool is_valid() noexcept { return contains(); } + static constexpr bool contains(T value) noexcept { - const auto result { std::equal_range(values.cbegin(), values.cend(), value, _value_comp) }; - return result.first != result.second; + if constexpr (is_continuous()) + return in_range(value); + else + return std::binary_search(values.cbegin(), values.cend(), value, _value_comp); } static constexpr bool contains(std::string_view str) noexcept { - const auto result { std::equal_range(sorted_entries.cbegin(), sorted_entries.cend(), enum_tuple(T{}, str), _tuple_comp_rev) }; - return result.first != result.second; + return std::binary_search(sorted_entries.cbegin(), sorted_entries.cend(), enum_tuple(T{}, str), _tuple_comp_rev); } + template + static constexpr bool contains() noexcept { return contains(e); } // string <==> enum template - static constexpr std::string_view enum_to_string() noexcept { return _get_name(); } + static constexpr std::string_view enum_to_string() noexcept { return _get_name_v; } static constexpr std::string_view enum_to_string(T value, [[maybe_unused]] bool noscope=false) noexcept { - if (const auto result { std::equal_range(entries.cbegin(), entries.cend(), enum_tuple(value, std::string_view()), _tuple_comp) }; - result.first != result.second) + if constexpr (is_continuous()) { + if (in_range(value)) + { #if not defined FIX8_CONJURE_ENUM_MINIMAL - if (noscope) - return remove_scope(std::get(*result.first)); + if (noscope) + return remove_scope(std::get(entries[enum_to_underlying(value)])); #endif - return std::get(*result.first); + return std::get(entries[enum_to_underlying(value)]); + } } - return {}; - } - static constexpr std::optional string_to_enum(std::string_view str) noexcept - { - if (const auto result { std::equal_range(sorted_entries.cbegin(), sorted_entries.cend(), enum_tuple(T{}, str), _tuple_comp_rev) }; - result.first != result.second) - return std::get(*result.first); - return {}; - } + else if (const auto [begin,end] { std::equal_range(entries.cbegin(), entries.cend(), enum_tuple(value, std::string_view()), _tuple_comp) }; + begin != end) + { #if not defined FIX8_CONJURE_ENUM_MINIMAL - static constexpr std::optional unscoped_string_to_enum(std::string_view str) noexcept - { - if (const auto result { std::equal_range(unscoped_entries.cbegin(), unscoped_entries.cend(), enum_tuple(T{}, str), _tuple_comp_rev) }; - result.first != result.second) - return std::get(*result.first); - return {}; - } + if (noscope) + return remove_scope(std::get(*begin)); #endif - - /// for_each, for_each_n - template - requires std::invocable - [[maybe_unused]] static constexpr auto for_each(Fn&& func, Args&&... args) noexcept - { - return for_each_n(static_cast(count()), std::forward(func), std::forward(args)...); - } - - template // specialisation for member function with object - requires std::invocable - [[maybe_unused]] static constexpr auto for_each(Fn&& func, C *obj, Args&&... args) noexcept - { - return for_each(std::bind(std::forward(func), obj, std::placeholders::_1, std::forward(args)...)); - } - - template - requires std::invocable - [[maybe_unused]] static constexpr auto for_each_n(int n, Fn&& func, Args&&... args) noexcept - { - for (int ii{}; const auto ev : conjure_enum::values) - { - if (ii++ >= n) - break; - std::invoke(std::forward(func), ev, std::forward(args)...); + return std::get(*begin); } - return std::bind(std::forward(func), std::placeholders::_1, std::forward(args)...); - } - - template // specialisation for member function with object - requires std::invocable - [[maybe_unused]] static constexpr auto for_each_n(int n, Fn&& func, C *obj, Args&&... args) noexcept - { - return for_each_n(n, std::bind(std::forward(func), obj, std::placeholders::_1, std::forward(args)...)); - } - - // dispatch - template - static constexpr bool tuple_comp(const std::tuple& pl, const std::tuple& pr) noexcept - { - return static_cast(std::get(pl)) < static_cast(std::get(pr)); - } - - template // with not found value(nval) for return - requires std::invocable - [[maybe_unused]] static constexpr R dispatch(T ev, R nval, const std::array, I>& disp, Args&&... args) noexcept - { - const auto result { std::equal_range(disp.cbegin(), disp.cend(), std::make_tuple(ev, Fn()), tuple_comp) }; - return result.first != result.second ? std::invoke(std::get(*result.first), ev, std::forward(args)...) : nval; - } - - template // specialisation for member function with not found value(nval) for return - requires std::invocable - [[maybe_unused]] static constexpr R dispatch(T ev, R nval, const std::array, I>& disp, C *obj, Args&&... args) noexcept - { - const auto result { std::equal_range(disp.cbegin(), disp.cend(), std::make_tuple(ev, Fn()), tuple_comp) }; - return result.first != result.second ? std::invoke(std::get(*result.first), obj, ev, std::forward(args)...) : nval; - } - - template // void func with not found call to last element - requires (std::invocable && I > 0) - static constexpr void dispatch(T ev, const std::array, I>& disp, Args&&... args) noexcept - { - const auto result { std::equal_range(disp.cbegin(), std::prev(disp.cend()), std::make_tuple(ev, Fn()), tuple_comp) }; - return result.first != result.second ? std::invoke(std::get(*result.first), ev, std::forward(args)...) - : std::invoke(std::get(*std::prev(disp.cend())), ev, std::forward(args)...); + return {}; } - - template // specialisation for void member function with not found call to last element - requires (std::invocable && I > 0) - static constexpr void dispatch(T ev, const std::array, I>& disp, C *obj, Args&&... args) noexcept + static constexpr std::optional string_to_enum(std::string_view str) noexcept { - const auto result { std::equal_range(disp.cbegin(), std::prev(disp.cend()), std::make_tuple(ev, Fn()), tuple_comp) }; - return result.first != result.second ? std::invoke(std::get(*result.first), obj, ev, std::forward(args)...) - : std::invoke(std::get(*std::prev(disp.cend())), obj, ev, std::forward(args)...); + const auto [begin,end] { std::equal_range(sorted_entries.cbegin(), sorted_entries.cend(), enum_tuple(T{}, str), _tuple_comp_rev) }; + return begin != end ? std::get(*begin) : std::optional{}; } // public constexpr data structures static constexpr auto values { _values(std::make_index_sequence()) }; - static constexpr auto entries { _entries(std::make_index_sequence()) }; - static constexpr auto names { _names(std::make_index_sequence()) }; + static constexpr auto entries { _entries(std::make_index_sequence()) }; static constexpr auto sorted_entries { _sorted_entries() }; -#if not defined FIX8_CONJURE_ENUM_MINIMAL - static constexpr auto scoped_entries { _scoped_entries(std::make_index_sequence()) }; - static constexpr auto unscoped_entries { _unscoped_entries(std::make_index_sequence()) }; - static constexpr auto rev_scoped_entries { _rev_scoped_entries(std::make_index_sequence()) }; - static constexpr auto unscoped_names { _unscoped_names(std::make_index_sequence()) }; -#endif // misc static constexpr int get_enum_min_value() noexcept { return enum_min_value; } static constexpr int get_enum_max_value() noexcept { return enum_max_value; } -}; - -//----------------------------------------------------------------------------------------- -// allow range based for -template -struct iterator_adaptor -{ - constexpr auto begin() noexcept { return conjure_enum::entries.cbegin(); } - constexpr auto end() noexcept { return conjure_enum::entries.cend(); } -}; - -//----------------------------------------------------------------------------------------- -// ostream& operator<< for any enum; add the following before using: -// using ostream_enum_operator::operator<<; -//----------------------------------------------------------------------------------------- -namespace ostream_enum_operator -{ - template, valid_enum T> - constexpr std::basic_ostream& operator<<(std::basic_ostream& os, T value) noexcept - { - if (conjure_enum::contains(value)) - return os << conjure_enum::enum_to_string(value); - return os << conjure_enum::enum_to_underlying(value); - } -} - -//----------------------------------------------------------------------------------------- -//----------------------------------------------------------------------------------------- -template -concept valid_bitset_enum = valid_enum and requires(T) -{ - requires static_cast(conjure_enum::values.back()) < conjure_enum::count(); -}; - -//----------------------------------------------------------------------------------------- -// bitset based on supplied enum -// Note: your enum sequence must be continuous with the last enum value < count of enumerations -//----------------------------------------------------------------------------------------- -template -class enum_bitset -{ - using U = std::underlying_type_t; - static constexpr auto countof { conjure_enum::count() }; - - template - static constexpr U to_underlying() noexcept { return static_cast(val); } // C++23: upgrade to std::to_underlying - static constexpr U to_underlying(T val) noexcept { return static_cast(val); } - static constexpr U all_bits { (1 << countof) - 1 }; - U _present{}; - -public: - explicit constexpr enum_bitset(U bits) noexcept : _present(bits) {} - constexpr enum_bitset(std::string_view from, bool anyscope=false, char sep='|', bool ignore_errors=true) - : _present(factory(from, anyscope, sep, ignore_errors)) {} - - template - constexpr enum_bitset(E... comp) noexcept : _present((0u | ... | (1 << to_underlying(comp)))) {} - - template - constexpr enum_bitset(I... comp) noexcept : _present((0u | ... | (1 << comp))) {} - - constexpr enum_bitset() = default; - constexpr ~enum_bitset() = default; - - constexpr std::size_t count() const noexcept - { return std::popcount(static_cast>(_present)); } // C++23: upgrade to std::bitset when count is constexpr - constexpr std::size_t not_count() const noexcept { return countof - count(); } - constexpr std::size_t size() const noexcept { return countof; } - constexpr U to_ulong() const noexcept { return _present; } - constexpr unsigned long long to_ullong() const noexcept { return _present; } - - constexpr bool operator[](std::size_t pos) const noexcept { return test(pos); } - constexpr bool operator[](T what) const noexcept { return test(what); } - - /// set - constexpr void set(U pos, bool value=true) noexcept { value ? _present |= 1 << pos : _present &= ~(1 << pos); } - constexpr void set(T what, bool value=true) noexcept { set(to_underlying(what), value); } - constexpr void set() noexcept { _present = all_bits; } - - template - constexpr void set() noexcept - { - if constexpr (constexpr auto uu{to_underlying()}; uu < countof) - _present |= 1 << uu; - } - - template - requires (sizeof...(comp) > 1) - constexpr void set() noexcept { (set(),...); } - - template - requires (sizeof...(E) > 1) - constexpr void set(E... comp) noexcept { return (... | (set(comp))); } - - /// flip - template - constexpr void flip() noexcept - { - if constexpr (constexpr auto uu{to_underlying()}; uu < countof) - _present ^= 1 << uu; - } - - constexpr void flip() noexcept { _present = ~_present & all_bits; } - constexpr void flip(U pos) noexcept { _present ^= 1 << pos; } - constexpr void flip(T what) noexcept { flip(to_underlying(what)); } - - /// reset - template - constexpr void reset() noexcept - { - if constexpr (constexpr auto uu{to_underlying()}; uu < countof) - _present &= ~(1 << uu); - } - - constexpr void reset() noexcept { _present = 0; } - constexpr void reset(U pos) noexcept { _present &= ~(1 << pos); } - constexpr void reset(T what) noexcept { reset(to_underlying(what)); } - - template - requires (sizeof...(comp) > 1) - constexpr void reset() noexcept { (reset(),...); } - - template - constexpr void reset(I...comp) noexcept { (reset(comp),...); } - - /// test - constexpr bool test(U pos) const noexcept { return _present & 1 << pos; } - constexpr bool test(T what) const noexcept { return test(to_underlying(what)); } - constexpr bool test() const noexcept { return _present; } - - template - constexpr bool test() const noexcept - { - if constexpr (constexpr auto uu{to_underlying()}; uu < countof) - return test(uu); - else - return false; - } - - template - constexpr bool any_of() const noexcept { return (... || test()); } - - template - constexpr bool any_of(I...comp) const noexcept { return (... || test(comp)); } - - template - constexpr bool any_of(E...comp) const noexcept { return (... || test(comp)); } - - template - constexpr bool all_of() const noexcept { return (... && test()); } - - template - constexpr bool all_of(I...comp) const noexcept { return (... && test(comp)); } - - template - constexpr bool all_of(E...comp) const noexcept { return (... && test(comp)); } - - template - constexpr bool none_of() const noexcept { return (... && !test()); } - - template - constexpr bool none_of(I...comp) const noexcept { return (... && !test(comp)); } - - template - constexpr bool none_of(E...comp) const noexcept { return (... && !test(comp)); } - - constexpr bool any() const noexcept { return count(); } - constexpr bool all() const noexcept { return _present == all_bits; } - constexpr bool none() const noexcept { return !*this; } - - /// operators - constexpr operator bool() const noexcept { return count(); } - constexpr enum_bitset& operator<<=(std::size_t pos) noexcept { _present <<= pos; return *this; } - constexpr enum_bitset& operator>>=(std::size_t pos) noexcept { _present >>= pos; return *this; } - constexpr enum_bitset& operator&=(T other) noexcept { _present &= 1 << to_underlying(other); return *this; } - constexpr enum_bitset& operator|=(T other) noexcept { _present |= 1 << to_underlying(other); return *this; } - constexpr enum_bitset& operator^=(T other) noexcept { _present ^= 1 << to_underlying(other); return *this; } - constexpr enum_bitset& operator&=(U other) noexcept { _present &= other; return *this; } - constexpr enum_bitset& operator|=(U other) noexcept { _present |= other; return *this; } - constexpr enum_bitset& operator^=(U other) noexcept { _present ^= other; return *this; } - - constexpr enum_bitset operator<<(int pos) const noexcept { return enum_bitset(_present << pos); } - constexpr enum_bitset operator>>(int pos) const noexcept { return enum_bitset(_present >> pos); } - constexpr enum_bitset operator&(T other) const noexcept { return enum_bitset(_present & 1 << to_underlying(other)); } - constexpr enum_bitset operator|(T other) const noexcept { return enum_bitset(_present | 1 << to_underlying(other)); } - constexpr enum_bitset operator^(T other) const noexcept { return enum_bitset(_present ^ 1 << to_underlying(other)); } - constexpr enum_bitset operator&(U other) const noexcept { return enum_bitset(_present & other); } - constexpr enum_bitset operator|(U other) const noexcept { return enum_bitset(_present | other); } - constexpr enum_bitset operator^(U other) const noexcept { return enum_bitset(_present ^ other); } - constexpr enum_bitset operator~() const noexcept { return enum_bitset(~_present & all_bits); } - - /// for_each, for_each_n - template - requires std::invocable - [[maybe_unused]] constexpr auto for_each(Fn&& func, Args&&... args) noexcept - { - return for_each_n(static_cast(countof), std::forward(func), std::forward(args)...); - } - - template // specialisation for member function with object - requires std::invocable - [[maybe_unused]] constexpr auto for_each(Fn&& func, C *obj, Args&&... args) noexcept - { - return for_each(std::bind(std::forward(func), obj, std::placeholders::_1, std::forward(args)...)); - } - - template - requires std::invocable - [[maybe_unused]] constexpr auto for_each_n(int n, Fn&& func, Args&&... args) noexcept - { - for (int ii{}, jj{}; ii < static_cast(countof) && jj < n; ++ii) - if (const auto ev{conjure_enum::values[ii]}; test(ev)) - std::invoke(std::forward(func), ev, std::forward(args)...), ++jj; - return std::bind(std::forward(func), std::placeholders::_1, std::forward(args)...); - } - - template // specialisation for member function with object - requires std::invocable - [[maybe_unused]] constexpr auto for_each_n(int n, Fn&& func, C *obj, Args&&... args) noexcept - { - return for_each_n(n, std::bind(std::forward(func), obj, std::placeholders::_1, std::forward(args)...)); - } - - /// create a bitset from custom separated enum string - static constexpr U factory(std::string_view src, bool anyscope, char sep, bool ignore_errors) - { - enum_bitset result; - constexpr auto trim([](std::string_view src) noexcept ->auto - { - const auto bg(src.find_first_not_of(" \t")); - return bg == std::string_view::npos ? src : src.substr(bg, src.find_last_not_of(" \t") - bg + 1); - }); - auto process([anyscope,&result](std::string_view src) noexcept ->auto - { - if (anyscope && !conjure_enum::has_scope(src)) - src = conjure_enum::add_scope(src); - if (auto ev { conjure_enum::string_to_enum(src) }; ev) - { - result |= *ev; - return true; - } - return false; - }); - for (std::string_view::size_type pos{}, fnd{};; pos = fnd + 1) - { - if ((fnd = src.find_first_of(sep, pos)) != std::string_view::npos) - { - if (auto srcp { trim(src.substr(pos, fnd - pos)) }; !process(trim(srcp)) && !ignore_errors) - throw std::invalid_argument(std::string(srcp).c_str()); - continue; - } - if (pos < src.size()) - if (auto srcp { trim(src.substr(pos, src.size() - pos)) }; !process(trim(srcp)) && !ignore_errors) - throw std::invalid_argument(std::string(srcp).c_str()); - break; - } - return result._present; - } + static constexpr auto min_v { values.front() }; + static constexpr auto max_v { values.back() }; + static constexpr int get_actual_enum_min_value() noexcept { return static_cast(min_v); } + static constexpr int get_actual_enum_max_value() noexcept { return static_cast(max_v); } - constexpr std::string to_string(char zero='0', char one='1') const noexcept - { - return std::bitset(_present).to_string(zero, one); - } - - friend constexpr std::ostream& operator<<(std::ostream& os, const enum_bitset& what) noexcept - { - return os << what.to_string(); - } -}; - -template -constexpr enum_bitset operator&(const enum_bitset& lh, const enum_bitset& rh) noexcept - { return lh.operator&(rh.to_ulong()); } -template -constexpr enum_bitset operator|(const enum_bitset& lh, const enum_bitset& rh) noexcept - { return lh.operator|(rh.to_ulong()); } -template -constexpr enum_bitset operator^(const enum_bitset& lh, const enum_bitset& rh) noexcept - { return lh.operator^(rh.to_ulong()); } - -//----------------------------------------------------------------------------------------- -// General purpose class allowing you to extract a string representation of any typename. -// The string will be stored statically by the compiler, so you can use the statically generated value `name` to obtain your type. -//----------------------------------------------------------------------------------------- -template -class conjure_type : public static_only -{ - static constexpr std::string_view _get_name() noexcept - { - constexpr std::string_view from{tpeek()}; -#if defined _MSC_VER - constexpr auto ep { from.rfind(cs::get_spec()) }; - if constexpr (ep == std::string_view::npos) - return {}; - if constexpr (constexpr auto lc { from.find_first_of(cs::get_spec()) }; lc != std::string_view::npos) - { - constexpr auto e1 { from.substr(lc + 1, ep - lc - 2) }; - CHKMSSTR(e1,type_t); - CHKMSSTR(e1,extype_t0); - CHKMSSTR(e1,extype_t1); - CHKMSSTR(e1,extype_t2); - CHKMSSTR(e1,extype_t3); - } - return {}; - } -#else - constexpr auto ep { from.rfind(cs::get_spec()) }; - if constexpr (ep == std::string_view::npos) - return {}; - if constexpr (from[ep + cs::get_spec().size()] == cs::get_spec()) - if (constexpr auto lstr { from.substr(ep + cs::get_spec().size()) }; - lstr.find(cs::get_spec()) != std::string_view::npos) // is anon - if constexpr (constexpr auto lc { lstr.find_first_of(cs::get_spec()) }; lc != std::string_view::npos) - return lstr.substr(cs::get_spec().size() + 2, lc - (cs::get_spec().size() + 2)); // eat "::" - constexpr auto result { from.substr(ep + cs::get_spec().size()) }; - if constexpr (constexpr auto lc { result.find_first_of(cs::get_spec()) }; lc != std::string_view::npos) - return result.substr(0, lc); - return {}; - } - static constexpr auto _type_name() noexcept - { - constexpr auto result { _get_name() }; - return fixed_string(result); - } -#endif - -public: - static consteval const char *tpeek() noexcept { return std::source_location::current().function_name(); } - static constexpr auto name - { -#if defined _MSC_VER - _get_name() -#else - _type_name() -#endif - }; - static constexpr std::string_view as_string_view() noexcept - { -#if defined _MSC_VER - return name; +#if not defined FIX8_CONJURE_ENUM_MINIMAL +#include #else - return name.get(); -#endif - } }; +#endif //----------------------------------------------------------------------------------------- } // FIX8 diff --git a/include/fix8/conjure_enum_bitset.hpp b/include/fix8/conjure_enum_bitset.hpp new file mode 100644 index 00000000..fc1f9dc9 --- /dev/null +++ b/include/fix8/conjure_enum_bitset.hpp @@ -0,0 +1,404 @@ +//----------------------------------------------------------------------------------------- +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: Copyright (C) 2024 Fix8 Market Technologies Pty Ltd +// SPDX-FileType: SOURCE +// +// conjure_enum (header only) +// by David L. Dight +// see https://github.com/fix8mt/conjure_enum +// +// Lightweight header-only C++20 enum and typename reflection +// +// Licensed under the MIT License . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice (including the next paragraph) +// shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +//---------------------------------------------------------------------------------------- +// enum_bitset +//---------------------------------------------------------------------------------------- +#ifndef FIX8_CONJURE_ENUM_BITSET_HPP_ +#define FIX8_CONJURE_ENUM_BITSET_HPP_ + +//---------------------------------------------------------------------------------------- +#include +#include +#include +#include +#if __has_include() +# include +#else +#include +#endif +#include + +//----------------------------------------------------------------------------------------- +namespace FIX8 { + +//----------------------------------------------------------------------------------------- +template +concept valid_bitset_enum = valid_enum and requires(T) +{ + requires conjure_enum::is_continuous(); + requires conjure_enum::get_actual_enum_min_value() == 0; + requires conjure_enum::get_actual_enum_max_value() < conjure_enum::count(); +}; + +//----------------------------------------------------------------------------------------- +// bitset based on supplied enum +// Note: your enum sequence must be 0 based, continuous and the last enum value < count of enumerations +//----------------------------------------------------------------------------------------- +template +class enum_bitset +{ + static constexpr auto countof { conjure_enum::count() }; + using U = std::conditional_t>>>; + static_assert(std::integral, "requested bitset overflow"); + + static constexpr U all_bits { (1 << countof) - 1 }; + static constexpr U unused_bits { sizeof(U) * 8 - countof }; + + template + class _reference + { + R& _owner; + U _idx; + constexpr _reference(R& obj, U idx) noexcept : _owner(obj), _idx(idx) {} + friend class enum_bitset; + + public: + constexpr _reference& operator=(bool val) noexcept + { + val ? _owner.set(_idx) : _owner.reset(_idx); + return *this; + } + + constexpr _reference& operator=(const _reference& val) noexcept + { + if (this != &val) + *this = static_cast(val); + return *this; + } + + constexpr operator bool() const noexcept { return _owner.test(_idx); } + }; + + template + static constexpr U to_underlying() noexcept { return static_cast(val); } // C++23: upgrade to std::to_underlying + static constexpr U to_underlying(T val) noexcept { return static_cast(val); } + U _present{}; + +#if __has_include() + static constexpr std::array _hexfmtarr { "{:#x}", "{:#X}", "{:x}", "{:#X}" }; +#endif + + static constexpr int correct_count(int val) noexcept { return val ? val - unused_bits : 0; } + +public: + using enum_bitset_underlying_type = U; + using reference = _reference; + using const_reference = _reference; + + explicit constexpr enum_bitset(U bits) noexcept : _present(bits) {} + explicit constexpr enum_bitset(std::bitset from) : _present(from.to_ullong()) {} + constexpr enum_bitset(std::string_view from, bool anyscope=false, char sep='|', bool ignore_errors=true) + : _present(factory(from, anyscope, sep, ignore_errors)) {} + + template + requires (sizeof...(E) > 1) + constexpr enum_bitset(E... comp) noexcept : _present((0u | ... | (1 << to_underlying(comp)))) {} + + template + requires (sizeof...(I) > 1) + constexpr enum_bitset(I... comp) noexcept : _present((0u | ... | (1 << comp))) {} + + constexpr enum_bitset() = default; + constexpr ~enum_bitset() = default; + + constexpr std::size_t count() const noexcept + { return std::popcount(static_cast>(_present)); } // C++23: upgrade to std::bitset when count is constexpr + constexpr std::size_t not_count() const noexcept { return countof - count(); } + constexpr int countl_zero() const noexcept { return correct_count(std::countl_zero(_present)); } + constexpr int countl_one() const noexcept { return correct_count(std::countl_one(_present)); } + constexpr int countr_zero() const noexcept { return std::countr_zero(_present); } + constexpr int countr_one() const noexcept { return std::countr_one(_present); } + constexpr std::size_t size() const noexcept { return countof; } + constexpr unsigned long to_ulong() const + { + if (std::bit_width(_present) > 32) + throw std::overflow_error("overflow"); + return _present; + } + constexpr unsigned long long to_ullong() const noexcept { return _present; } + constexpr U get_underlying() const noexcept { return _present; } + constexpr int get_underlying_bit_size() const noexcept { return 8 * sizeof(U); } + constexpr U get_bit_mask() const noexcept { return all_bits; } + constexpr U get_unused_bit_mask() const noexcept { return ((1 << get_underlying_bit_size()) - 1) ^ all_bits; } + + // subscript + constexpr auto operator[](U pos) noexcept { return reference(*this, pos); } + constexpr auto operator[](U pos) const noexcept { return const_reference(*this, pos); } + constexpr auto operator[](T what) noexcept { return (*this)[to_underlying(what)]; } + constexpr auto operator[](T what) const noexcept { return (*this)[to_underlying(what)]; } + + /// set + constexpr void set(U pos, bool value=true) noexcept { value ? _present |= 1 << pos : _present &= ~(1 << pos); } + constexpr void set(T what, bool value=true) noexcept { set(to_underlying(what), value); } + constexpr void set() noexcept { _present = all_bits; } + + template + constexpr void set() noexcept + { + if constexpr (constexpr auto uu{to_underlying()}; uu < countof) + _present |= 1 << uu; + } + + template + requires (sizeof...(comp) > 1) + constexpr void set() noexcept { (set(),...); } + + template + requires (sizeof...(E) > 1) + constexpr void set(E... comp) noexcept { return (... | (set(comp))); } + + /// flip + template + constexpr void flip() noexcept + { + if constexpr (constexpr auto uu{to_underlying()}; uu < countof) + _present ^= 1 << uu; + } + + constexpr void flip() noexcept { _present = ~_present & all_bits; } + constexpr void flip(U pos) noexcept { _present ^= 1 << pos; } + constexpr void flip(T what) noexcept { flip(to_underlying(what)); } + + /// rotate + constexpr enum_bitset& rotl(int cnt) noexcept { set(_present << cnt | _present >> (countof * 8 - cnt)); return *this; } + constexpr enum_bitset& rotr(int cnt) noexcept { set(_present >> cnt | _present << (countof * 8 - cnt)); return *this; } + + /// reset + template + constexpr void reset() noexcept + { + if constexpr (constexpr auto uu{to_underlying()}; uu < countof) + _present &= ~(1 << uu); + } + + constexpr void reset() noexcept { _present = 0; } + constexpr void reset(U pos) noexcept { _present &= ~(1 << pos); } + constexpr void reset(T what) noexcept { reset(to_underlying(what)); } + + template + requires (sizeof...(comp) > 1) + constexpr void reset() noexcept { (reset(),...); } + + template + constexpr void reset(I...comp) noexcept { (reset(comp),...); } + + /// test + constexpr bool test(U pos) const noexcept { return _present & 1 << pos; } + constexpr bool test(T what) const noexcept { return test(to_underlying(what)); } + constexpr bool test() const noexcept { return _present; } + + template + constexpr bool test() const noexcept + { + if constexpr (constexpr auto uu{to_underlying()}; uu < countof) + return test(uu); + else + return false; + } + + template + constexpr bool any_of() const noexcept { return (... || test()); } + + template + constexpr bool any_of(I...comp) const noexcept { return (... || test(U(comp))); } + + template + constexpr bool any_of(E...comp) const noexcept { return (... || test(U(comp))); } + + template + constexpr bool all_of() const noexcept { return (... && test()); } + + template + constexpr bool all_of(I...comp) const noexcept { return (... && test(U(comp))); } + + template + constexpr bool all_of(E...comp) const noexcept { return (... && test(U(comp))); } + + template + constexpr bool none_of() const noexcept { return (... && !test()); } + + template + constexpr bool none_of(I...comp) const noexcept { return (... && !test(U(comp))); } + + template + constexpr bool none_of(E...comp) const noexcept { return (... && !test(U(comp))); } + + constexpr bool any() const noexcept { return count(); } + constexpr bool all() const noexcept { return _present == all_bits; } + constexpr bool none() const noexcept { return !*this; } + constexpr bool has_single_bit() const noexcept { return std::has_single_bit(_present); } + + /// operators + constexpr enum_bitset& operator<<=(std::size_t pos) noexcept { _present <<= pos; return *this; } + constexpr enum_bitset& operator>>=(std::size_t pos) noexcept { _present >>= pos; return *this; } + constexpr enum_bitset& operator&=(T other) noexcept { _present &= 1 << to_underlying(other); return *this; } + constexpr enum_bitset& operator|=(T other) noexcept { _present |= 1 << to_underlying(other); return *this; } + constexpr enum_bitset& operator^=(T other) noexcept { _present ^= 1 << to_underlying(other); return *this; } + constexpr enum_bitset& operator&=(U other) noexcept { _present &= other; return *this; } + constexpr enum_bitset& operator|=(U other) noexcept { _present |= other; return *this; } + constexpr enum_bitset& operator^=(U other) noexcept { _present ^= other; return *this; } + + constexpr enum_bitset operator<<(int pos) const noexcept { return enum_bitset(_present << pos); } + constexpr enum_bitset operator>>(int pos) const noexcept { return enum_bitset(_present >> pos); } + constexpr enum_bitset operator&(T other) const noexcept { return enum_bitset(_present & 1 << to_underlying(other)); } + constexpr enum_bitset operator|(T other) const noexcept { return enum_bitset(_present | 1 << to_underlying(other)); } + constexpr enum_bitset operator^(T other) const noexcept { return enum_bitset(_present ^ 1 << to_underlying(other)); } + constexpr enum_bitset operator~() const noexcept { return enum_bitset(~_present & all_bits); } + + constexpr operator auto() const noexcept { return std::bitset(_present); } + constexpr operator bool() const noexcept { return count(); } + + /// for_each, for_each_n + template + requires std::invocable + [[maybe_unused]] constexpr auto for_each(Fn&& func, Args&&... args) noexcept + { + return for_each_n(static_cast(countof), std::forward(func), std::forward(args)...); + } + + template // specialisation for member function with object + requires std::invocable + [[maybe_unused]] constexpr auto for_each(Fn&& func, C *obj, Args&&... args) noexcept + { + return for_each(std::bind(std::forward(func), obj, std::placeholders::_1, std::forward(args)...)); + } + + template + requires std::invocable + [[maybe_unused]] constexpr auto for_each_n(int n, Fn&& func, Args&&... args) noexcept + { + for (int ii{}, jj{}; ii < static_cast(countof) && jj < n; ++ii) + if (const auto ev{conjure_enum::values[ii]}; test(ev)) + std::invoke(std::forward(func), ev, std::forward(args)...), ++jj; + return std::bind(std::forward(func), std::placeholders::_1, std::forward(args)...); + } + + template // specialisation for member function with object + requires std::invocable + [[maybe_unused]] constexpr auto for_each_n(int n, Fn&& func, C *obj, Args&&... args) noexcept + { + return for_each_n(n, std::bind(std::forward(func), obj, std::placeholders::_1, std::forward(args)...)); + } + + /// create a bitset from custom separated enum string + static constexpr U factory(std::string_view src, bool anyscope, char sep, bool ignore_errors) + { + enum_bitset result; + constexpr auto trim([](std::string_view src) noexcept ->auto + { + const auto bg(src.find_first_not_of(" \t")); + return bg == std::string_view::npos ? src : src.substr(bg, src.find_last_not_of(" \t") - bg + 1); + }); + auto process([anyscope,&result](std::string_view src) noexcept ->auto + { + if (anyscope && !conjure_enum::has_scope(src)) + src = conjure_enum::add_scope(src); + if (auto ev { conjure_enum::string_to_enum(src) }; ev) + { + result |= *ev; + return true; + } + return false; + }); + for (std::string_view::size_type pos{}, fnd{};; pos = fnd + 1) + { + if ((fnd = src.find_first_of(sep, pos)) != std::string_view::npos) + { + if (auto srcp { trim(src.substr(pos, fnd - pos)) }; !process(trim(srcp)) && !ignore_errors) + throw std::invalid_argument(std::string(srcp).c_str()); + continue; + } + if (pos < src.size()) + if (auto srcp { trim(src.substr(pos, src.size() - pos)) }; !process(trim(srcp)) && !ignore_errors) + throw std::invalid_argument(std::string(srcp).c_str()); + break; + } + return result._present; + } + + constexpr std::string to_string(char zero='0', char one='1') const noexcept + { + return std::bitset(_present).to_string(zero, one); + } + + template +#if __has_include() + constexpr std::string to_hex_string() const noexcept + { + return std::format(_hexfmtarr[(showbase ? 0 : 2) + (uppercase ? 1 : 0)], _present); + } +#else + std::string to_hex_string() const noexcept + { + std::ostringstream ostr; + if (showbase) + ostr << std::showbase; + if (uppercase) + ostr << std::uppercase; + ostr << std::hex << _present; + return ostr.str(); + } +#endif + constexpr std::string to_hex_string() const noexcept { return to_hex_string<>(); } + + friend constexpr std::ostream& operator<<(std::ostream& os, const enum_bitset& what) noexcept + { + return os << what.to_string(); + } +}; + +template +constexpr enum_bitset operator&(const enum_bitset& lh, const enum_bitset& rh) noexcept + { return lh.operator&(rh.to_ulong()); } +template +constexpr enum_bitset operator|(const enum_bitset& lh, const enum_bitset& rh) noexcept + { return lh.operator|(rh.to_ulong()); } +template +constexpr enum_bitset operator^(const enum_bitset& lh, const enum_bitset& rh) noexcept + { return lh.operator^(rh.to_ulong()); } + +//----------------------------------------------------------------------------------------- +} // FIX8 + +/// std::hash specialization for enum_bitset. +template +struct std::hash> +{ + size_t operator()(const FIX8::enum_bitset& bs) const noexcept + { + return std::hash::enum_bitset_underlying_type>()(bs.get_underlying()); + } +}; + +#endif // FIX8_CONJURE_ENUM_BITSET_HPP_ + diff --git a/include/fix8/conjure_enum_ext.hpp b/include/fix8/conjure_enum_ext.hpp new file mode 100644 index 00000000..154052a5 --- /dev/null +++ b/include/fix8/conjure_enum_ext.hpp @@ -0,0 +1,261 @@ +//----------------------------------------------------------------------------------------- +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: Copyright (C) 2024 Fix8 Market Technologies Pty Ltd +// SPDX-FileType: SOURCE +// +// conjure_enum (header only) +// by David L. Dight +// see https://github.com/fix8mt/conjure_enum +// +// Lightweight header-only C++20 enum and typename reflection +// +// Parts based on magic_enum +// Copyright (c) 2019 - 2024 Daniil Goncharov . +// +// Licensed under the MIT License . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice (including the next paragraph) +// shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +//---------------------------------------------------------------------------------------- +// Full build include fragment; do not include separately +//---------------------------------------------------------------------------------------- +#ifndef FIX8_CONJURE_ENUM_EXT_HPP_ +#define FIX8_CONJURE_ENUM_EXT_HPP_ + +//---------------------------------------------------------------------------------------- +private: + static constexpr bool _scoped_comp(const scoped_tuple& pl, const scoped_tuple& pr) noexcept + { + return std::get<0>(pl) < std::get<0>(pr); + } + + template + static constexpr auto _names(std::index_sequence) noexcept + { + return std::array{{{ _enum_name_v}...}}; + } + + static constexpr std::string_view _remove_scope(std::string_view what) noexcept + { + if (const auto lc { what.find_last_of(':') }; lc != std::string_view::npos) + return what.substr(lc + 1); + return what; + } + + template + static constexpr auto _unscoped_entries(std::index_sequence) noexcept + { + std::array tmp{{{ values[I], _remove_scope(_enum_name_v)}...}}; + std::sort(tmp.begin(), tmp.end(), _tuple_comp_rev); + return tmp; + } + + template + static constexpr auto _scoped_entries(std::index_sequence) noexcept + { + std::array tmp{{{ _remove_scope(_enum_name_v), _enum_name_v}...}}; + std::sort(tmp.begin(), tmp.end(), _scoped_comp); + return tmp; + } + + template + static constexpr auto _rev_scoped_entries(std::index_sequence) noexcept + { + std::array tmp{{{ _enum_name_v, _remove_scope(_enum_name_v)}...}}; + std::sort(tmp.begin(), tmp.end(), _scoped_comp); + return tmp; + } + + template + static constexpr auto _unscoped_names(std::index_sequence) noexcept + { + return std::array{{{ _remove_scope(_enum_name_v)}...}}; + } + + static constexpr std::string_view _process_scope([[maybe_unused]] const auto& entr, std::string_view what) noexcept + { + if constexpr (is_scoped()) + if (const auto [begin,end] { std::equal_range(entr.cbegin(), entr.cend(), scoped_tuple(what, std::string_view()), _scoped_comp) }; + begin != end) + return std::get<1>(*begin); + return what; + } + +public: + static constexpr std::string_view remove_scope(std::string_view what) noexcept + { + return _process_scope(rev_scoped_entries, what); + } + + static constexpr std::string_view add_scope(std::string_view what) noexcept + { + return _process_scope(scoped_entries, what); + } + + // iterators + static constexpr auto cbegin() noexcept { return entries.cbegin(); } + static constexpr auto cend() noexcept { return entries.cend(); } + static constexpr auto crbegin() noexcept { return entries.crbegin(); } + static constexpr auto crend() noexcept { return entries.crend(); } + static constexpr auto front() noexcept { return *cbegin(); } + static constexpr auto back() noexcept { return *std::prev(cend()); } + static constexpr std::optional unscoped_string_to_enum(std::string_view str) noexcept + { + const auto [begin,end] { std::equal_range(unscoped_entries.cbegin(), unscoped_entries.cend(), enum_tuple(T{}, str), _tuple_comp_rev) }; + return begin != end ? std::get(*begin) : std::optional{}; + } + + static constexpr std::string_view type_name() noexcept + { + constexpr std::string_view from{tpeek()}; +#if defined _MSC_VER + constexpr auto ep { from.rfind(cs::get_spec()) }; + if constexpr (ep == std::string_view::npos) + return {}; + if constexpr (constexpr auto lc { from.find_first_of(cs::get_spec()) }; lc != std::string_view::npos) + { + constexpr auto e1 { from.substr(lc + 1, ep - lc - 2) }; + CHKMSSTR(e1,type_t); + CHKMSSTR(e1,extype_t1); + } + else + return {}; +#else + if constexpr (constexpr auto ep { from.rfind(cs::get_spec()) }; ep != std::string_view::npos) + { + constexpr auto result { from.substr(ep + cs::get_spec().size()) }; + if constexpr (constexpr auto lc { result.find_first_of(cs::get_spec()) }; lc != std::string_view::npos) + return result.substr(0, lc); + } + else + return {}; +#endif + } + + /// for_each, for_each_n + template + requires std::invocable + [[maybe_unused]] static constexpr auto for_each(Fn&& func, Args&&... args) noexcept + { + return for_each_n(static_cast(count()), std::forward(func), std::forward(args)...); + } + + template // specialisation for member function with object + requires std::invocable + [[maybe_unused]] static constexpr auto for_each(Fn&& func, C *obj, Args&&... args) noexcept + { + return for_each(std::bind(std::forward(func), obj, std::placeholders::_1, std::forward(args)...)); + } + + template + requires std::invocable + [[maybe_unused]] static constexpr auto for_each_n(int n, Fn&& func, Args&&... args) noexcept + { + for (int ii{}; const auto ev : conjure_enum::values) + { + if (ii++ >= n) + break; + std::invoke(std::forward(func), ev, std::forward(args)...); + } + return std::bind(std::forward(func), std::placeholders::_1, std::forward(args)...); + } + + template // specialisation for member function with object + requires std::invocable + [[maybe_unused]] static constexpr auto for_each_n(int n, Fn&& func, C *obj, Args&&... args) noexcept + { + return for_each_n(n, std::bind(std::forward(func), obj, std::placeholders::_1, std::forward(args)...)); + } + + // dispatch + template + static constexpr bool tuple_comp(const std::tuple& pl, const std::tuple& pr) noexcept + { + return std::get(pl) < std::get(pr); + } + + template // with not found value(nval) for return + requires std::invocable + [[maybe_unused]] static constexpr R dispatch(T ev, R nval, const std::array, I>& disp, Args&&... args) noexcept + { + const auto [begin,end] { std::equal_range(disp.cbegin(), disp.cend(), std::make_tuple(ev, Fn()), tuple_comp) }; + return begin != end ? std::invoke(std::get(*begin), ev, std::forward(args)...) : nval; + } + + template // specialisation for member function with not found value(nval) for return + requires std::invocable + [[maybe_unused]] static constexpr R dispatch(T ev, R nval, const std::array, I>& disp, C *obj, Args&&... args) noexcept + { + const auto [begin,end] { std::equal_range(disp.cbegin(), disp.cend(), std::make_tuple(ev, Fn()), tuple_comp) }; + return begin != end ? std::invoke(std::get(*begin), obj, ev, std::forward(args)...) : nval; + } + + template // void func with not found call to last element + requires (std::invocable && I > 0) + static constexpr void dispatch(T ev, const std::array, I>& disp, Args&&... args) noexcept + { + const auto [begin,end] { std::equal_range(disp.cbegin(), std::prev(disp.cend()), std::make_tuple(ev, Fn()), tuple_comp) }; + return std::invoke(std::get(begin != end ? *begin : *std::prev(disp.cend())), ev, std::forward(args)...); + } + + template // specialisation for void member function with not found call to last element + requires (std::invocable && I > 0) + static constexpr void dispatch(T ev, const std::array, I>& disp, C *obj, Args&&... args) noexcept + { + const auto [begin,end] { std::equal_range(disp.cbegin(), std::prev(disp.cend()), std::make_tuple(ev, Fn()), tuple_comp) }; + return std::invoke(std::get(begin != end ? *begin : *std::prev(disp.cend())), obj, ev, std::forward(args)...); + } + + // public constexpr data structures + static constexpr auto names { _names(std::make_index_sequence()) }; + static constexpr auto scoped_entries { _scoped_entries(std::make_index_sequence()) }; + static constexpr auto unscoped_entries { _unscoped_entries(std::make_index_sequence()) }; + static constexpr auto rev_scoped_entries { _rev_scoped_entries(std::make_index_sequence()) }; + static constexpr auto unscoped_names { _unscoped_names(std::make_index_sequence()) }; +}; + +//----------------------------------------------------------------------------------------- +// allow range based for +template +struct iterator_adaptor +{ + constexpr auto begin() noexcept { return conjure_enum::entries.cbegin(); } + constexpr auto end() noexcept { return conjure_enum::entries.cend(); } +}; + +//----------------------------------------------------------------------------------------- +#include + +//----------------------------------------------------------------------------------------- +// ostream& operator<< for any enum; add the following before using: +// using ostream_enum_operator::operator<<; +//----------------------------------------------------------------------------------------- +namespace ostream_enum_operator +{ + template, valid_enum T> + constexpr std::basic_ostream& operator<<(std::basic_ostream& os, T value) noexcept + { + if (conjure_enum::contains(value)) + return os << conjure_enum::enum_to_string(value); + return os << conjure_enum::enum_to_underlying(value); + } +} + +//----------------------------------------------------------------------------------------- + +#endif // FIX8_CONJURE_ENUM_EXT_HPP_ + diff --git a/include/fix8/conjure_type.hpp b/include/fix8/conjure_type.hpp new file mode 100644 index 00000000..d78a6737 --- /dev/null +++ b/include/fix8/conjure_type.hpp @@ -0,0 +1,109 @@ +//----------------------------------------------------------------------------------------- +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: Copyright (C) 2024 Fix8 Market Technologies Pty Ltd +// SPDX-FileType: SOURCE +// +// conjure_enum (header only) +// by David L. Dight +// see https://github.com/fix8mt/conjure_enum +// +// Lightweight header-only C++20 enum and typename reflection +// +// Licensed under the MIT License . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice (including the next paragraph) +// shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +//---------------------------------------------------------------------------------------- +// conjure_type +//---------------------------------------------------------------------------------------- +#ifndef FIX8_CONJURE_TYPE_HPP_ +#define FIX8_CONJURE_TYPE_HPP_ + +//----------------------------------------------------------------------------------------- +namespace FIX8 { + +//----------------------------------------------------------------------------------------- +// General purpose class allowing you to extract a string representation of any typename. +// The string will be stored statically by the compiler, so you can use the statically generated value `name` to obtain your type. +//----------------------------------------------------------------------------------------- +template +class conjure_type : public static_only +{ + static constexpr std::string_view _get_name() noexcept + { + constexpr std::string_view from{tpeek()}; +#if defined _MSC_VER + constexpr auto ep { from.rfind(cs::get_spec()) }; + if constexpr (ep == std::string_view::npos) + return {}; + if constexpr (constexpr auto lc { from.find_first_of(cs::get_spec()) }; lc != std::string_view::npos) + { + constexpr auto e1 { from.substr(lc + 1, ep - lc - 2) }; + CHKMSSTR(e1,type_t); + CHKMSSTR(e1,extype_t0); + CHKMSSTR(e1,extype_t1); + CHKMSSTR(e1,extype_t2); + CHKMSSTR(e1,extype_t3); + } + return {}; + } +#else + constexpr auto ep { from.rfind(cs::get_spec()) }; + if constexpr (ep == std::string_view::npos) + return {}; + if constexpr (from[ep + cs::get_spec().size()] == cs::get_spec()) + if (constexpr auto lstr { from.substr(ep + cs::get_spec().size()) }; + lstr.find(cs::get_spec()) != std::string_view::npos) // is anon + if constexpr (constexpr auto lc { lstr.find_first_of(cs::get_spec()) }; lc != std::string_view::npos) + return lstr.substr(cs::get_spec().size() + 2, lc - (cs::get_spec().size() + 2)); // eat "::" + constexpr auto result { from.substr(ep + cs::get_spec().size()) }; + if constexpr (constexpr auto lc { result.find_first_of(cs::get_spec()) }; lc != std::string_view::npos) + return result.substr(0, lc); + return {}; + } + static constexpr auto _type_name() noexcept + { + constexpr auto result { _get_name() }; + return fixed_string(result); + } +#endif + +public: + static consteval const char *tpeek() noexcept { return std::source_location::current().function_name(); } + static constexpr auto name + { +#if defined _MSC_VER + _get_name() +#else + _type_name() +#endif + }; + static constexpr std::string_view as_string_view() noexcept + { +#if defined _MSC_VER + return name; +#else + return name.get(); +#endif + } +}; + +//----------------------------------------------------------------------------------------- +} // FIX8 + +#endif // FIX8_CONJURE_TYPE_HPP_ + diff --git a/utests/edgetests.cpp b/utests/edgetests.cpp index 9a95cfa2..0e7f1abe 100644 --- a/utests/edgetests.cpp +++ b/utests/edgetests.cpp @@ -31,18 +31,22 @@ #include #include #include +#include +#include //----------------------------------------------------------------------------------------- class foobat{}; +#define ENUMS One, Two, Three, Four, Five, Six, Seven, Eight, Nine + namespace { - enum class NineEnums : int { One, Two, Three, Four, Five, Six, Seven, Eight, Nine }; - enum NineEnums1 : int { One, Two, Three, Four, Five, Six, Seven, Eight, Nine }; + enum class NineEnums : int { ENUMS }; + enum NineEnums1 : int { ENUMS }; namespace TEST1 { - enum class NineEnums : int { One, Two, Three, Four, Five, Six, Seven, Eight, Nine }; - enum NineEnums1 : int { One, Two, Three, Four, Five, Six, Seven, Eight, Nine }; + enum class NineEnums : int { ENUMS }; + enum NineEnums1 : int { ENUMS }; class foo{}; } class foo{}; @@ -50,12 +54,12 @@ namespace namespace TEST { - enum class NineEnums : int { One, Two, Three, Four, Five, Six, Seven, Eight, Nine }; - enum NineEnums1 : int { One, Two, Three, Four, Five, Six, Seven, Eight, Nine }; + enum class NineEnums : int { ENUMS }; + enum NineEnums1 : int { ENUMS }; namespace TEST1 { - enum class NineEnums : int { One, Two, Three, Four, Five, Six, Seven, Eight, Nine }; - enum NineEnums1 : int { One, Two, Three, Four, Five, Six, Seven, Eight, Nine }; + enum class NineEnums : int { ENUMS }; + enum NineEnums1 : int { ENUMS }; class foo{}; } class foo{}; diff --git a/utests/unittests.cpp b/utests/unittests.cpp index 82af360e..278794fd 100644 --- a/utests/unittests.cpp +++ b/utests/unittests.cpp @@ -34,6 +34,8 @@ #include #include #include +#include +#include //----------------------------------------------------------------------------------------- using namespace FIX8; @@ -48,6 +50,25 @@ enum class directions { left, right, up, down, forward, backward, notfound=-1 }; enum class range_test { first, second, third, fourth, fifth, sixth, seventh, eighth }; enum class range_test1 { first, second, third, fourth, fifth, sixth, seventh, eighth }; enum class range_test2 { first, second, third, fourth, fifth, sixth, seventh, eighth }; +enum class range_test3 { first, second, third, fourth, fifth, sixth, seventh, eighth, ce_first=first, ce_last=eighth }; +enum range_test4 { first, second, third, fourth, fifth, sixth, seventh, eighth, ce_first=first, ce_last=eighth }; +enum class numbers64 : uint64_t +{ + zero, one, two, three, four, + five, six, seven, eight, nine, + ten, eleven, twelve, thirteen, fourteen, + fifteen, sixteen, seventeen, eighteen, nineteen, + twenty, twenty_one, twenty_two, twenty_three, twenty_four, + twenty_five, twenty_six, twenty_seven, twenty_eight, twenty_nine, + thirty, thirty_one, thirty_two, thirty_three, thirty_four, + thirty_five, thirty_six, thirty_seven, thirty_eight, thirty_nine, + forty, forty_one, forty_two, forty_three, forty_four, + forty_five, forty_six, forty_seven, forty_eight, forty_nine, + fifty, fifty_one, fifty_two, fifty_three, fifty_four, + fifty_five, fifty_six, fifty_seven, fifty_eight, fifty_nine, + sixty, sixty_one, sixty_two, sixty_three +}; +enum class reverse_range_test { first=7, second=6, third=5, fourth=4, fifth=3, sixth=2, seventh=1, eighth=0 }; //----------------------------------------------------------------------------------------- // run as: ctest --output-on-failure @@ -55,12 +76,15 @@ enum class range_test2 { first, second, third, fourth, fifth, sixth, seventh, ei TEST_CASE("fixed_string") { static constexpr std::string_view t1{"The rain in Spain"}; - fixed_string f1{t1}; + constexpr fixed_string f1{t1}; REQUIRE(f1.size() == t1.size() + 1); // fixed_string makes string ASCIIZ REQUIRE(f1[t1.size()] == 0); // test ASCIIZ REQUIRE(f1.get().size() == t1.size()); // fixed_string as string_view REQUIRE(static_cast(f1) == t1); // fixed_string as string_view REQUIRE(static_cast(f1).size() == t1.size()); // fixed_string as string_view + std::ostringstream ostr; + ostr << f1.c_str(); + REQUIRE(ostr.str() == "The rain in Spain"); } //----------------------------------------------------------------------------------------- @@ -68,6 +92,12 @@ TEST_CASE("default range") { REQUIRE(conjure_enum::get_enum_min_value() == FIX8_CONJURE_ENUM_MIN_VALUE); REQUIRE(conjure_enum::get_enum_max_value() == FIX8_CONJURE_ENUM_MAX_VALUE); + REQUIRE(conjure_enum::get_actual_enum_min_value() == 0); + REQUIRE(conjure_enum::get_actual_enum_max_value() == 14); + REQUIRE(conjure_enum::get_enum_min_value() == FIX8_CONJURE_ENUM_MIN_VALUE); + REQUIRE(conjure_enum::get_enum_max_value() == FIX8_CONJURE_ENUM_MAX_VALUE); + REQUIRE(conjure_enum::get_actual_enum_min_value() == 0); + REQUIRE(conjure_enum::get_actual_enum_max_value() == 7); } //----------------------------------------------------------------------------------------- @@ -83,12 +113,25 @@ TEST_CASE("custom range") { REQUIRE(conjure_enum::get_enum_min_value() == 0); REQUIRE(conjure_enum::get_enum_max_value() == 7); + REQUIRE(conjure_enum::get_enum_min_value() == conjure_enum::get_actual_enum_min_value()); + REQUIRE(conjure_enum::get_enum_max_value() == conjure_enum::get_actual_enum_max_value()); REQUIRE(conjure_enum::get_enum_min_value() == 0); REQUIRE(conjure_enum::get_enum_max_value() == 7); REQUIRE(conjure_enum::get_enum_min_value() == 0); REQUIRE(conjure_enum::get_enum_max_value() == 7); } +//----------------------------------------------------------------------------------------- +TEST_CASE("custom range (alias)") +{ + REQUIRE(conjure_enum::get_enum_min_value() == 0); + REQUIRE(conjure_enum::get_enum_max_value() == 7); + REQUIRE(conjure_enum::get_enum_min_value() == conjure_enum::get_actual_enum_min_value()); + REQUIRE(conjure_enum::get_enum_max_value() == conjure_enum::get_actual_enum_max_value()); + REQUIRE(conjure_enum::get_enum_min_value() == 0); + REQUIRE(conjure_enum::get_enum_max_value() == 7); +} + //----------------------------------------------------------------------------------------- TEST_CASE("is_valid") { @@ -105,11 +148,29 @@ TEST_CASE("is_scoped") REQUIRE(!conjure_enum::is_scoped()); } +//----------------------------------------------------------------------------------------- +TEST_CASE("is_continuous") +{ + REQUIRE(!conjure_enum::is_continuous()); + REQUIRE(conjure_enum::is_continuous()); + REQUIRE(conjure_enum::is_continuous()); +} + //----------------------------------------------------------------------------------------- TEST_CASE("count") { REQUIRE(conjure_enum::count() == 10); REQUIRE(conjure_enum::count() == 10); + REQUIRE(conjure_enum::count() == 10); +} + +//----------------------------------------------------------------------------------------- +TEST_CASE("in_range") +{ + REQUIRE(conjure_enum::in_range(component::password)); + REQUIRE(!conjure_enum::in_range(static_cast(100))); + REQUIRE(conjure_enum::in_range(numbers::five)); + REQUIRE(!conjure_enum::in_range(static_cast(100))); } //----------------------------------------------------------------------------------------- @@ -212,6 +273,11 @@ TEST_CASE("contains") REQUIRE(!conjure_enum::contains(static_cast(100))); REQUIRE(conjure_enum::contains("component::path"sv)); REQUIRE(conjure_enum::contains("path"sv)); + REQUIRE(conjure_enum::contains()); + REQUIRE(conjure_enum::contains()); // alias + REQUIRE(conjure_enum::contains()); + REQUIRE(conjure_enum::contains(numbers::five)); + REQUIRE(!conjure_enum::contains(static_cast(100))); } //----------------------------------------------------------------------------------------- @@ -223,6 +289,8 @@ TEST_CASE("enum_to_string") REQUIRE(conjure_enum::enum_to_string(static_cast(100)).empty()); REQUIRE(conjure_enum::enum_to_string() == "component::fragment"); REQUIRE(conjure_enum::enum_to_string() == "fragment"); + using enum numbers; + REQUIRE(conjure_enum::enum_to_string() == "numbers::two"); } //----------------------------------------------------------------------------------------- @@ -259,6 +327,10 @@ TEST_CASE("iterators") REQUIRE(std::get(conjure_enum::front()) == scheme); REQUIRE(std::get(conjure_enum::back()) == fragment); REQUIRE(std::get(conjure_enum::back()) == std::get(*conjure_enum::crbegin())); + REQUIRE(std::get(conjure_enum::front()) == reverse_range_test::eighth); + REQUIRE(std::get(conjure_enum::back()) == reverse_range_test::first); + REQUIRE(std::get(conjure_enum::front()) == conjure_enum::min_v); + REQUIRE(std::get(conjure_enum::back()) == conjure_enum::max_v); } //----------------------------------------------------------------------------------------- @@ -293,6 +365,9 @@ TEST_CASE("int_to_enum") REQUIRE(conjure_enum::int_to_enum(4).value() == password); REQUIRE(conjure_enum::int_to_enum(11).value_or(static_cast(100)) == static_cast(100)); REQUIRE(conjure_enum::int_to_enum(11).value_or(static_cast(100)) == static_cast(100)); + REQUIRE(conjure_enum::int_to_enum(4).value() == numbers::four); + REQUIRE(conjure_enum::int_to_enum(11).value_or(static_cast(100)) == static_cast(100)); + REQUIRE(conjure_enum::enum_cast(150) == static_cast(150)); } //----------------------------------------------------------------------------------------- @@ -304,6 +379,21 @@ TEST_CASE("enum_to_int") REQUIRE(conjure_enum::enum_to_underlying(password) == 4); } +//----------------------------------------------------------------------------------------- +TEST_CASE("index") +{ + REQUIRE(conjure_enum::index(component::scheme).value() == 0); + REQUIRE(conjure_enum::index(component::password).value() == 4); + REQUIRE(conjure_enum::index(component::query).value() == 8); + REQUIRE(conjure_enum::index(component(100)).value_or(100) == 100); + REQUIRE(conjure_enum::index().value() == 0); + REQUIRE(conjure_enum::index().value() == 4); + REQUIRE(conjure_enum::index().value() == 8); + REQUIRE(conjure_enum::index().value_or(100) == 100); + REQUIRE(conjure_enum::index().value() == 5); + REQUIRE(conjure_enum::index().value_or(100) == 100); +} + //----------------------------------------------------------------------------------------- TEST_CASE("ostream<<") { @@ -559,6 +649,9 @@ TEST_CASE("enum_bitset") REQUIRE(ec.to_ulong() == 0b0001001010); REQUIRE(ec.to_string('-', '+') == "---+--+-+-"s); REQUIRE(enum_bitset(0b0101001010).to_string() == "0101001010"s); + REQUIRE(ec.to_hex_string() == "0x4a"s); + REQUIRE(ec.to_hex_string() == "4a"s); + REQUIRE(ec.to_hex_string() == "0X4A"s); REQUIRE(ec.test()); ec.flip(); @@ -579,6 +672,27 @@ TEST_CASE("enum_bitset") ec.set(numbers::three, false); REQUIRE(ec.test() == false); REQUIRE(ec.any()); + REQUIRE(enum_bitset().get_underlying_bit_size() == 8); + REQUIRE(ec.get_underlying_bit_size() == 16); + REQUIRE(ec.get_unused_bit_mask() == 0b111111 << 10); + REQUIRE(ec.get_bit_mask() == 0b1111111111); + + ec.reset(); + ec.set(); + REQUIRE(ec.countl_one() == 0); + REQUIRE(ec.countr_zero() == 1); + REQUIRE(ec.countr_one() == 0); + REQUIRE(ec.countl_zero() == 6); +} + +//----------------------------------------------------------------------------------------- +TEST_CASE("enum_bitset <==> std::bitset") +{ + std::bitset<10> bs{1 << 1 | 1 << 3 | 1 << 6}; + enum_bitset ed(bs); + REQUIRE(ed.to_ulong() == (1 << 1 | 1 << 3 | 1 << 6)); + std::bitset<10> bs1{ed}; + REQUIRE(bs1.to_ulong() == (1 << 1 | 1 << 3 | 1 << 6)); } //----------------------------------------------------------------------------------------- @@ -605,6 +719,30 @@ TEST_CASE("enum_bitset ops") REQUIRE((ed ^ numbers::one).to_ulong() == 0b010); ed ^= numbers::one; REQUIRE(ed.to_ulong() == 0b010); + + ed.reset(); + ed[2] = true; + REQUIRE(ed.test(numbers::two)); + REQUIRE(ed[2] == true); + ed.reset(); + ed[numbers::two] = true; + REQUIRE(ed.test(numbers::two)); + REQUIRE(ed[2] == true); + + ed.reset(); + ed.set(); + REQUIRE(ed.rotl(1) == enum_bitset(numbers::two,numbers::four,numbers::seven)); + REQUIRE(ed.rotr(1) == enum_bitset(numbers::one,numbers::three,numbers::six)); + REQUIRE(ed.rotr(1) == enum_bitset(numbers::nine,numbers::two,numbers::five)); + REQUIRE(ed.rotl(4) == enum_bitset(numbers::eight,numbers::one,numbers::four)); + + ed.reset(); + ed.set(); + REQUIRE(ed.has_single_bit()); + ed.set(); + REQUIRE(!ed.has_single_bit()); + + REQUIRE(std::hash>{}(ed) == 14); } //----------------------------------------------------------------------------------------- @@ -625,13 +763,18 @@ TEST_CASE("enum_bitset ext ops") REQUIRE(ee.not_count() == 10 - 2); } +//----------------------------------------------------------------------------------------- +TEST_CASE("enum_bitset::to_ulong overflow") +{ + REQUIRE_NOTHROW(enum_bitset(0b1111111111111).to_ulong()); + REQUIRE_THROWS_AS(enum_bitset(0xfffffffffffffffe).to_ulong(), std::overflow_error); +} + //----------------------------------------------------------------------------------------- TEST_CASE("enum_bitset(std::string_view)") { REQUIRE_THROWS_MATCHES(enum_bitset("zero,twenty,two,three", true, ',', false), std::invalid_argument, Catch::Matchers::Message("twenty")); - enum_bitset sc("zero,two,three", true, ','); - REQUIRE(sc.to_ulong() == 0b1101); } //-----------------------------------------------------------------------------------------