diff --git a/EmilysRulesForSERiFCode.md b/EmilysRulesForSERiFCode.md new file mode 100644 index 0000000..28024f6 --- /dev/null +++ b/EmilysRulesForSERiFCode.md @@ -0,0 +1,40 @@ +# Emily's Rules for writing good SERiF code +If you will bear with me as I indulge in some prose. Astronomers are, as a rule, bad at programming. +We are a discipline focused on the abstract, though not that abstraction that is code. There are many +effects this has had on astrophysical code, though I would argue that the primary effect is +a paucity of good code. Do not misunderstand me reader, I do think there is good astrophysical +code that exists; it is only that I think that more good code should exist, that more good code could exist. + +There is perhaps an inescapable degree of egotism inherent in acting as the initial and lead developer +for a code such as this. I have, by necessity, had to impose my own views on what makes a software product +pleasant to develop for and pleasant to use. These are likely wrong, in so far as any views can be wrong. +I think it likely that no one is 'correct' in these things. Rather, what is important is that there be some +views. Yes you could, I'm sure, poke holes in any one of the points below. However, these points +have been more or less agreed upon for this code base and therefor should be followed, for consistency if not +for correctness. + +- Always remember the users of the code are astronomers not developers. All user facing code *must* be understandable by a senior undergraduate physics major. +- We are writing physics, user facing code should always prefer to describe physical intent. +- Other concerns (e.g. memory, numerics, IO, etc...) may be accessible through options; however, those options names should make it clear to users that they are straying into dangerous water +- The hierarchy of abstraction is physics > numerics > IO > memory. A user should need to put more effort into adjusting numerics than physics, and more effort into adjusting IO and memory than numerics. +- Here we write a library not an application. Do not concern yourself with such petty things as an entry point, command line arguments, etc... Rather, we provide tools for others to build applications with. +- The tools we provide should allow a user to construct a stellar model in less than 20 lines of code. +- Those same tools should also provide options which allow an advanced user to take near full control over their numerics and physics. +- Code should be self documenting, vowels don't bite. Leave them in your names. +- Developers are also astronomers, code should therefore not try to be too clever with syntax tricks. +- At the same time languages are advanced and complex, do not prevent yourself from writing code just because it is may be hard to understand. If you think that your way is best then it likely is. Just make sure you explain why you are doing what you are doing in a comment. +- Write comments that explain why not what +- All functions must have a docstring. +- Prefer compile time verification over runtime verification. +- Compile time invariants should be exercised with static_asserts in the compile_time_checks static library. A failing invariant should prevent the code from compiling +- Use the minimum header set you can +- Do not use exceptions for normal control flow. +- The library can throw exceptions; however, generally only the user should catch them. +- Within the library error states should be reported as a value (std::optional, std::expected, etc...) +- When an exception is thrown make it detailed. It should include what went wrong, where it went wrong, why it is wrong, if applicable what was received instead, and what the user can do to fix it. +- Prefer more and smaller files over fewer and larger files. +- Prefer more and smaller classes over fewer and larger classes. +- Prefer more and smaller functions over fewer and larger functions. +- Do not include the full mfem.hpp header. Its slow, I will send you an angry email. +- AI is a tool, its okay to use it, but use it as a tool not as a crutch. You must understand all the code you write. +- The less something is related to physics the more you may consider AI. A build system configuration for example is a good candidate for AI. A physics function is not. \ No newline at end of file diff --git a/build-config/magicenum/LICENSE b/build-config/magicenum/LICENSE new file mode 100644 index 0000000..8c755ae --- /dev/null +++ b/build-config/magicenum/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 - 2026 Daniil Goncharov + +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 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. diff --git a/build-config/magicenum/README.md b/build-config/magicenum/README.md new file mode 100644 index 0000000..434192a --- /dev/null +++ b/build-config/magicenum/README.md @@ -0,0 +1,375 @@ +[![Github releases](https://img.shields.io/github/release/Neargye/magic_enum.svg)](https://github.com/Neargye/magic_enum/releases) +[![Conan package](https://img.shields.io/badge/Conan-package-blueviolet)](https://conan.io/center/recipes/magic_enum) +[![Vcpkg package](https://img.shields.io/badge/Vcpkg-package-blueviolet)](https://github.com/microsoft/vcpkg/tree/master/ports/magic-enum) +[![Build2 package](https://img.shields.io/badge/Build2-package-blueviolet)](https://www.cppget.org/magic_enum?q=magic_enum) +[![Meson wrap](https://img.shields.io/badge/Meson-wrap-blueviolet)](https://github.com/mesonbuild/wrapdb/blob/master/subprojects/magic_enum.wrap) +[![License](https://img.shields.io/github/license/Neargye/magic_enum.svg)](LICENSE) +[![Compiler explorer](https://img.shields.io/badge/compiler_explorer-online-blue.svg)](https://godbolt.org/z/feqcPa5G6) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/Neargye/magic_enum/badge)](https://securityscorecards.dev/viewer/?uri=github.com/Neargye/magic_enum) + +# Magic Enum C++ + +Header-only C++17 library provides static reflection for enums, work with any enum type without any macro or boilerplate code. + +## Documentation + +* [Reference](doc/reference.md) +* [Limitations](doc/limitations.md) +* [Integration](#integration) + +## [Features & Examples](example/) + +* Basic + + ```cpp + #include + #include + + enum class Color { RED = -10, BLUE = 0, GREEN = 10 }; + + int main() { + Color c1 = Color::RED; + std::cout << magic_enum::enum_name(c1) << std::endl; // RED + return 0; + } + ``` + +* Enum value to string + + ```cpp + Color color = Color::RED; + auto color_name = magic_enum::enum_name(color); + // color_name -> "RED" + ``` + +* String to enum value + + ```cpp + std::string color_name{"GREEN"}; + auto color = magic_enum::enum_cast(color_name); + if (color.has_value()) { + // color.value() -> Color::GREEN + } + + // case insensitive enum_cast + auto color_case_insensitive = magic_enum::enum_cast(color_name, magic_enum::case_insensitive); + + // enum_cast with BinaryPredicate + auto color_with_predicate = magic_enum::enum_cast(color_name, [](char lhs, char rhs) { return std::tolower(static_cast(lhs)) == std::tolower(static_cast(rhs)); }); + + // enum_cast with default + auto color_or_default = magic_enum::enum_cast(color_name).value_or(Color::RED); + ``` + +* Integer to enum value + + ```cpp + int color_integer = 0; + auto color = magic_enum::enum_cast(color_integer); + if (color.has_value()) { + // color.value() -> Color::BLUE + } + + auto color_or_default = magic_enum::enum_cast(123).value_or(Color::RED); + ``` + +* Indexed access to enum value + + ```cpp + std::size_t i = 0; + Color color = magic_enum::enum_value(i); + // color -> Color::RED + ``` + +* Enum value sequence + + ```cpp + constexpr auto colors = magic_enum::enum_values(); + // colors -> {Color::RED, Color::BLUE, Color::GREEN} + // colors[0] -> Color::RED + ``` + +* Number of enum values + + ```cpp + constexpr std::size_t color_count = magic_enum::enum_count(); + // color_count -> 3 + ``` + +* Enum value to integer + + ```cpp + Color color = Color::RED; + auto color_integer = magic_enum::enum_integer(color); // or magic_enum::enum_underlying(color); + // color_integer -> -10 + ``` + +* Enum name sequence + + ```cpp + constexpr auto color_names = magic_enum::enum_names(); + // color_names -> {"RED", "BLUE", "GREEN"} + // color_names[0] -> "RED" + ``` + +* Enum entry sequence + + ```cpp + constexpr auto color_entries = magic_enum::enum_entries(); + // color_entries -> {{Color::RED, "RED"}, {Color::BLUE, "BLUE"}, {Color::GREEN, "GREEN"}} + // color_entries[0].first -> Color::RED + // color_entries[0].second -> "RED" + ``` + +* Enum fusion for multi-level switch/case statements + + ```cpp + switch (magic_enum::enum_fuse(color, direction).value()) { + case magic_enum::enum_fuse(Color::RED, Directions::Up).value(): // ... + case magic_enum::enum_fuse(Color::BLUE, Directions::Down).value(): // ... + // ... + } + ``` + +* Runtime enum value as constexpr constant + + ```cpp + Color color = Color::RED; + magic_enum::enum_switch([](auto val) { + constexpr Color c_color = val; + // ... + }, color); + ``` + +* Iterate over enum values as constexpr constants + + ```cpp + magic_enum::enum_for_each([](auto val) { + constexpr Color c_color = val; + // ... + }); + ``` + +* Move through enum values + + ```cpp + magic_enum::enum_next_value(Color::RED); // -> optional containing Color::BLUE + magic_enum::enum_prev_value_circular(Color::RED); // -> Color::GREEN + ``` + +* Check whether enum contains value + + ```cpp + magic_enum::enum_contains(Color::GREEN); // -> true + magic_enum::enum_contains(0); // -> true + magic_enum::enum_contains(123); // -> false + magic_enum::enum_contains("GREEN"); // -> true + magic_enum::enum_contains("fda"); // -> false + ``` + +* Check whether value can be reflected + + ```cpp + magic_enum::enum_reflected(Color::GREEN); // -> true + ``` + +* Enum index in sequence + + ```cpp + constexpr auto color_index = magic_enum::enum_index(Color::BLUE); + // color_index.value() -> 1 + // color_index.has_value() -> true + ``` + +* Flag operations + + ```cpp + enum Directions : std::uint64_t { + Left = 1, + Down = 2, + Up = 4, + Right = 8, + }; + template <> + struct magic_enum::customize::enum_range { + static constexpr bool is_flags = true; + }; + using namespace magic_enum::bitwise_operators; // Use with care; operators are enabled for all enums. + + magic_enum::enum_flags_name(Directions::Up | Directions::Right); // -> "Up|Right" + magic_enum::enum_flags_name(Directions::Up | Directions::Right, ','); // -> "Up,Right" + magic_enum::enum_flags_contains(Directions::Up | Directions::Right); // -> true + magic_enum::enum_flags_cast(3).value(); // -> Directions::Left|Directions::Down + magic_enum::enum_flags_cast("Left,Down", ',').value(); // -> Directions::Left|Directions::Down + magic_enum::enum_flags_test(Directions::Up | Directions::Right, Directions::Up); // -> true + magic_enum::enum_flags_test_any(Directions::Left | Directions::Down, Directions::Down | Directions::Right); // -> true + magic_enum::is_flags_v; // -> true + ``` + +* Enum type name + + ```cpp + Color color = Color::RED; + auto type_name = magic_enum::enum_type_name(); + // type_name -> "Color" + ``` + +* I/O stream operators for enums + + ```cpp + using magic_enum::iostream_operators::operator<<; // out-of-the-box ostream operators for enums. + Color color = Color::BLUE; + std::cout << color << std::endl; // "BLUE" + ``` + + ```cpp + using magic_enum::iostream_operators::operator>>; // out-of-the-box istream operators for enums. + Color color; + std::cin >> color; + ``` + +* Bitwise operators for enums + + ```cpp + enum class Flags { A = 1 << 0, B = 1 << 1, C = 1 << 2, D = 1 << 3 }; + using namespace magic_enum::bitwise_operators; // Use with care; operators are enabled for all enums. + // Support operators: ~, |, &, ^, |=, &=, ^=. + Flags flags = Flags::A | (Flags::B & ~Flags::C); + ``` + +* Formatting + + ```cpp + #include + #include + + std::format("{}", Color::RED); // -> "RED" + std::format("{}", Color{42}); // -> "42" + ``` + + Include `{fmt}` before `magic_enum_format.hpp` to enable `{fmt}` formatter support. + +* [Unscoped enum](https://en.cppreference.com/w/cpp/language/enum#Unscoped_enumeration) trait + + ```cpp + enum color { red, green, blue }; + enum class direction { left, right }; + + magic_enum::is_unscoped_enum_v -> true + magic_enum::is_unscoped_enum_v -> false + ``` + +* [Scoped enum](https://en.cppreference.com/w/cpp/language/enum#Scoped_enumerations) trait + + ```cpp + enum color { red, green, blue }; + enum class direction { left, right }; + + magic_enum::is_scoped_enum_v -> false + magic_enum::is_scoped_enum_v -> true + ``` + +* Compile-time enum value to string. This overload compiles faster and is not restricted by `enum_range` [limitation](doc/limitations.md). + + ```cpp + constexpr Color color = Color::BLUE; + constexpr auto color_name = magic_enum::enum_name(); + // color_name -> "BLUE" + ``` + +* `containers::array` array container for enums. + + ```cpp + constexpr auto color_rgb_values = magic_enum::containers::make_array(RGB{255, 0, 0}, RGB{0, 255, 0}, RGB{0, 0, 255}); + + magic_enum::containers::array color_rgb_array {}; + color_rgb_array[Color::RED] = {255, 0, 0}; + color_rgb_array[Color::GREEN] = {0, 255, 0}; + color_rgb_array[Color::BLUE] = {0, 0, 255}; + magic_enum::containers::get(color_rgb_array); // -> RGB{0, 0, 255} + ``` + +* `containers::bitset` bitset container for enums. + + ```cpp + constexpr magic_enum::containers::bitset color_bitset {Color::RED, Color::GREEN}; + color_bitset.test(Color::RED); // -> true + color_bitset.test(Color::BLUE); // -> false + + std::uint8_t incoming = 0b00000011; + auto raw_bitset = magic_enum::containers::bitset {magic_enum::containers::raw_access, incoming}; + ``` + +* `containers::set` set container for enums. + + ```cpp + auto color_set = magic_enum::containers::set(); + bool empty = color_set.empty(); + // empty -> true + color_set.insert(Color::GREEN); + color_set.insert(Color::BLUE); + color_set.insert(Color::RED); + std::size_t size = color_set.size(); + // size -> 3 + + using color_name_set = magic_enum::containers::set>; + color_name_set colors_by_name {Color::RED, Color::GREEN, Color::BLUE}; + ``` + +* [Underlying type](https://en.cppreference.com/w/cpp/types/underlying_type) + + ```cpp + magic_enum::underlying_type::type -> int + magic_enum::underlying_type_t -> int + ``` + +## Integration + +* Copy required headers from [`include/magic_enum`](include/magic_enum) or use [release archive](https://github.com/Neargye/magic_enum/releases/latest). `magic_enum_all.hpp` includes all public headers. +* Use CMake with `add_subdirectory` or `find_package(magic_enum CONFIG REQUIRED)`, then link `magic_enum::magic_enum`. +* Use [vcpkg](https://github.com/microsoft/vcpkg/tree/master/ports/magic-enum), [Conan](https://conan.io/center/recipes/magic_enum), [Build2](https://cppget.org/magic_enum?q=magic_enum), or [Meson](https://github.com/mesonbuild/wrapdb/blob/master/subprojects/magic_enum.wrap). +* Fetch sources with CMake [`FetchContent`](https://cmake.org/cmake/help/latest/module/FetchContent.html) or [CPM.cmake](https://github.com/cpm-cmake/CPM.cmake). Release tags use `vx.y.z` format. +* Use Bazel with `MODULE.bazel` or `http_archive`; target is `@magic_enum//:magic_enum`. +* Use ROS with `magic_enum` in `package.xml`, then link `magic_enum::magic_enum`. + +* **CMake targets**: + - `magic_enum::magic_enum` is the header-only target. + - `magic_enum::magic_enum_module` is the C++20 module target. Enable it with `MAGIC_ENUM_USE_MODULES=ON`. CMake 3.28+ is required. + + Build the module target: + ```sh + cmake -S . -B build -G Ninja -DMAGIC_ENUM_USE_MODULES=ON + cmake --build build + ``` + + Link the module target: + ```cmake + find_package(magic_enum CONFIG REQUIRED) + target_link_libraries(your_executable PRIVATE magic_enum::magic_enum_module) + set_target_properties(your_executable PROPERTIES CXX_EXTENSIONS OFF CXX_SCAN_FOR_MODULES ON) + ``` + + Import the module: + ```cpp + import magic_enum; + + enum class Color { RED, GREEN, BLUE }; + auto name = magic_enum::enum_name(Color::RED); // "RED" + ``` + + Do not use `#include ` and `import magic_enum;` in the same program. Use the same compiler, standard library, and C++ standard when building and consuming an installed module. The pkg-config package supports only the header-only target. + + Optional settings: + - Set `MAGIC_ENUM_MODULE_WITH_FMT=ON` to enable `{fmt}` support through `fmt::fmt`. It is disabled by default. The `{fmt}` C++ module is not supported. + - Set `MAGIC_ENUM_MODULE_IMPORT_STD=ON` to enable `import std` support. This requires a compatible CMake toolchain. + +## Header-only compiler compatibility + +* Clang/LLVM >= 5 +* MSVC++ >= 15.3 / Visual Studio >= 2017 +* Xcode >= 10 +* GCC >= 9 + +C++26 reflection is selected automatically when available; see [limitations](doc/limitations.md#c26-standard-reflection). + +## Licensed under the [MIT License](LICENSE) diff --git a/build-config/magicenum/SECURITY.md b/build-config/magicenum/SECURITY.md new file mode 100644 index 0000000..533224d --- /dev/null +++ b/build-config/magicenum/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +Security updates are applied only to the latest release. + +## Reporting a Vulnerability + +If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released. + +Please disclose it at [security advisory](https://github.com/Neargye/magic_enum/security/advisories/new). + +This project is maintained by a team of volunteers on a reasonable-effort basis. As such, vulnerabilities will be disclosed in a best effort base. diff --git a/build-config/magicenum/include/magic_enum/magic_enum.hpp b/build-config/magicenum/include/magic_enum/magic_enum.hpp new file mode 100644 index 0000000..df21d50 --- /dev/null +++ b/build-config/magicenum/include/magic_enum/magic_enum.hpp @@ -0,0 +1,2066 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.8 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2026 Daniil Goncharov . +// +// 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 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. + +#ifndef NEARGYE_MAGIC_ENUM_HPP +#define NEARGYE_MAGIC_ENUM_HPP + +#define MAGIC_ENUM_VERSION_MAJOR 0 +#define MAGIC_ENUM_VERSION_MINOR 9 +#define MAGIC_ENUM_VERSION_PATCH 8 + +#ifndef MAGIC_ENUM_USE_STD_MODULE +# include +# include +# include +# include +# include +# include +# include +#endif + +#if defined(MAGIC_ENUM_CONFIG_FILE) +# include MAGIC_ENUM_CONFIG_FILE +#endif + +// MAGIC_ENUM_USE_STD_MODULE imports the standard library before this header. +#if !defined(MAGIC_ENUM_FORCE_COMPILER_SPECIFIC_REFLECTION) && defined(__cpp_impl_reflection) && __cpp_impl_reflection >= 202506L && defined(__cpp_expansion_statements) && __cpp_expansion_statements >= 202506L +# if !defined(MAGIC_ENUM_USE_STD_MODULE) && defined(__has_include) +# if __has_include() +# include +# endif +# endif +# if defined(__cpp_lib_reflection) && __cpp_lib_reflection >= 202506L && defined(__cpp_lib_define_static) && __cpp_lib_define_static >= 202506L +# define MAGIC_ENUM_DETAIL_USE_STD_REFLECTION 1 +# endif +#endif + +#ifndef MAGIC_ENUM_USE_STD_MODULE +# if defined(MAGIC_ENUM_DETAIL_USE_STD_REFLECTION) +# include +# endif +# if !defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL) +# include +# endif +# if !defined(MAGIC_ENUM_USING_ALIAS_STRING) +# include +# endif +# if !defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW) +# include +# endif +#endif + +#if defined(MAGIC_ENUM_NO_ASSERT) && defined(MAGIC_ENUM_ASSERT) +# error MAGIC_ENUM_NO_ASSERT and MAGIC_ENUM_ASSERT cannot be used together. +#elif defined(MAGIC_ENUM_NO_ASSERT) +# define MAGIC_ENUM_ASSERT(...) static_cast(0) +#elif !defined(MAGIC_ENUM_ASSERT) +# include +# define MAGIC_ENUM_ASSERT(...) assert((__VA_ARGS__)) +#endif + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunknown-warning-option" +# pragma clang diagnostic ignored "-Wenum-constexpr-conversion" +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 28020) // MSVC analyzer loses constexpr array bounds in template instantiations. +# pragma warning(disable : 4514) // Unreferenced inline function has been removed. +#endif + +// Checks magic_enum compiler compatibility. +#if defined(MAGIC_ENUM_DETAIL_USE_STD_REFLECTION) || defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1910 || defined(__RESHARPER__) +# undef MAGIC_ENUM_SUPPORTED +# define MAGIC_ENUM_SUPPORTED 1 +#endif + +// Checks magic_enum compiler aliases compatibility. +#if defined(MAGIC_ENUM_DETAIL_USE_STD_REFLECTION) || defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1920 +# undef MAGIC_ENUM_SUPPORTED_ALIASES +# define MAGIC_ENUM_SUPPORTED_ALIASES 1 +#endif + +// Specify calling convention for compilers that need it to produce reliable mangled names under different compiler flags. In particular, MSVC allows changing default calling convention on x86. +#if defined(__clang__) || defined(__GNUC__) +#define MAGIC_ENUM_CALLING_CONVENTION +#elif defined(_MSC_VER) +#define MAGIC_ENUM_CALLING_CONVENTION __cdecl +#elif defined(MAGIC_ENUM_DETAIL_USE_STD_REFLECTION) +#define MAGIC_ENUM_CALLING_CONVENTION +#endif + +// Enum value must be greater or equals than MAGIC_ENUM_RANGE_MIN. By default MAGIC_ENUM_RANGE_MIN = -128. +// If need another min range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN. +#if !defined(MAGIC_ENUM_RANGE_MIN) +# define MAGIC_ENUM_RANGE_MIN -128 +#endif + +// Enum value must be less or equals than MAGIC_ENUM_RANGE_MAX. By default MAGIC_ENUM_RANGE_MAX = 127. +// If need another max range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MAX. +#if !defined(MAGIC_ENUM_RANGE_MAX) +# define MAGIC_ENUM_RANGE_MAX 127 +#endif + +// Improve ReSharper C++ intellisense performance with builtins, avoiding unnecessary template instantiations. +#if defined(__RESHARPER__) +# undef MAGIC_ENUM_GET_ENUM_NAME_BUILTIN +# undef MAGIC_ENUM_GET_TYPE_NAME_BUILTIN +# if __RESHARPER__ >= 20230100 +# define MAGIC_ENUM_GET_ENUM_NAME_BUILTIN(V) __rscpp_enumerator_name(V) +# define MAGIC_ENUM_GET_TYPE_NAME_BUILTIN(T) __rscpp_type_name() +# else +# define MAGIC_ENUM_GET_ENUM_NAME_BUILTIN(V) nullptr +# define MAGIC_ENUM_GET_TYPE_NAME_BUILTIN(T) nullptr +# endif +#endif + +namespace magic_enum { + +// If need another optional type, define the macro MAGIC_ENUM_USING_ALIAS_OPTIONAL. +#if defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL) +MAGIC_ENUM_USING_ALIAS_OPTIONAL +#else +using std::optional; +#endif + +// If need another string_view type, define the macro MAGIC_ENUM_USING_ALIAS_STRING_VIEW. +#if defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW) +MAGIC_ENUM_USING_ALIAS_STRING_VIEW +#else +using std::string_view; +#endif + +// If need another string type, define the macro MAGIC_ENUM_USING_ALIAS_STRING. +#if defined(MAGIC_ENUM_USING_ALIAS_STRING) +MAGIC_ENUM_USING_ALIAS_STRING +#else +using std::string; +#endif + +using char_type = string_view::value_type; +static_assert(std::is_same_v, "magic_enum::customize requires same string_view::value_type and string::value_type"); +#if defined(MAGIC_ENUM_DETAIL_USE_STD_REFLECTION) +static_assert(std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v, + "magic_enum standard reflection requires string_view::value_type to be a standard character type; define MAGIC_ENUM_FORCE_COMPILER_SPECIFIC_REFLECTION before including magic_enum.hpp for other character types."); +#endif +static_assert([] { + if constexpr (std::is_same_v) { + constexpr const char c[] = "abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789|"; + constexpr const wchar_t wc[] = L"abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789|"; + static_assert(std::size(c) == std::size(wc), "magic_enum::customize identifier characters are multichars in wchar_t."); + + for (std::size_t i = 0; i < std::size(c); ++i) { + if (c[i] != wc[i]) { + return false; + } + } + } + return true; +}(), "magic_enum::customize wchar_t is not compatible with ASCII."); + +namespace customize { + +template +struct enum_range; + +} // namespace magic_enum::customize + +namespace detail { + +template +inline constexpr std::size_t prefix_length_v = 0; + +template +inline constexpr auto prefix_length_v::prefix_length)>> = std::size_t{customize::enum_range::prefix_length}; + +constexpr bool valid_prefix_length(std::size_t prefix, std::size_t name_size) noexcept { + return prefix == 0 || prefix < name_size; +} + +} // namespace magic_enum::detail + +namespace customize { + +template +struct adl_info_holder { + static constexpr int min = Min; + static constexpr int max = Max; + static constexpr bool is_flags = IsFlags; + static constexpr std::size_t prefix_length = PrefixLength; + + template + static constexpr adl_info_holder minmax() { return {}; } + + template + static constexpr adl_info_holder flag() { return {}; } + + template + static constexpr adl_info_holder prefix() { return {}; } +}; + +constexpr adl_info_holder<> adl_info() { return {}; } + +// Compiler-specific reflection scans common enum values in [min, max]. Redefine MAGIC_ENUM_RANGE_MIN/MAX globally or specialize enum_range for a specific enum. +template +struct enum_range { + static constexpr int min = MAGIC_ENUM_RANGE_MIN; + static constexpr int max = MAGIC_ENUM_RANGE_MAX; +}; + +template +struct enum_range : decltype(magic_enum_define_range_adl(E{})) {}; + +namespace detail { + +enum class customize_tag { + default_tag, + invalid_tag, + custom_tag +}; + +} // namespace magic_enum::customize::detail + +class customize_t : public std::pair { + public: + constexpr customize_t(string_view srt) : std::pair{detail::customize_tag::custom_tag, srt} {} + constexpr customize_t(const char_type* srt) : customize_t{string_view{srt}} {} + constexpr customize_t(detail::customize_tag tag) : std::pair{tag, string_view{}} { + MAGIC_ENUM_ASSERT(tag != detail::customize_tag::custom_tag); + } +}; + +// Default customize. +inline constexpr auto default_tag = customize_t{detail::customize_tag::default_tag}; +// Invalid customize. +inline constexpr auto invalid_tag = customize_t{detail::customize_tag::invalid_tag}; + +// If need custom names for enum, add specialization enum_name for necessary enum type. +template +constexpr customize_t enum_name(E) noexcept { + return default_tag; +} + +// If need custom type name for enum, add specialization enum_type_name for necessary enum type. +template +constexpr customize_t enum_type_name() noexcept { + return default_tag; +} + +} // namespace magic_enum::customize + +namespace detail { + +template +struct supported +#if defined(MAGIC_ENUM_SUPPORTED) || defined(MAGIC_ENUM_NO_CHECK_SUPPORT) + : std::true_type {}; +#else + : std::false_type {}; +#endif + +template , std::enable_if_t, int> = 0> +using enum_constant = std::integral_constant; + +template +inline constexpr bool always_false_v = false; + +template +struct has_is_flags : std::false_type {}; + +template +struct has_is_flags::is_flags)>> : std::true_type { + static_assert(std::is_same_v::is_flags)>>, "magic_enum::customize::enum_range requires is_flags to have bool type."); +}; + +template +struct range_min : std::integral_constant {}; + +template +struct range_min::min)>> : std::integral_constant::min), customize::enum_range::min> {}; + +template +struct range_max : std::integral_constant {}; + +template +struct range_max::max)>> : std::integral_constant::max), customize::enum_range::max> {}; + +struct cname_ref { + const char* str_ = nullptr; + std::size_t size_ = 0; +}; + +template +constexpr bool enum_value_equal(E lhs, E rhs) noexcept { + using U = std::underlying_type_t; + return static_cast(lhs) == static_cast(rhs); +} + +template +constexpr bool enum_value_equal(E lhs, std::underlying_type_t rhs) noexcept { + return static_cast>(lhs) == rhs; +} + +#if defined(MAGIC_ENUM_DETAIL_USE_STD_REFLECTION) + +namespace reflection { + +template +consteval auto enumerators() noexcept { + if constexpr (std::meta::is_enumerable_type(^^E)) { + return std::define_static_array(std::meta::enumerators_of(^^E)); + } else { + static_assert(always_false_v, "magic_enum requires a complete enum definition."); + return std::array{}; + } +} + +template +inline constexpr auto enumerators_v = enumerators(); + +template +consteval auto type_name() noexcept { + if constexpr (std::meta::has_identifier(^^E)) { + constexpr auto identifier = std::meta::identifier_of(^^E); + return cname_ref{identifier.data(), identifier.size()}; + } else { + return cname_ref{}; + } +} + +template > +consteval auto enum_name() noexcept { + template for (constexpr auto enumerator : enumerators_v) { + if constexpr (enum_value_equal([:enumerator:], V)) { + constexpr auto identifier = std::meta::identifier_of(enumerator); + return cname_ref{identifier.data(), identifier.size()}; // Preserve one-name-per-value: first declaration wins. + } + } + return cname_ref{}; +} + +template +constexpr bool contains_underlying([[maybe_unused]] std::underlying_type_t value) noexcept { + template for (constexpr auto enumerator : enumerators_v) { + if (enum_value_equal([:enumerator:], value)) { + return true; + } + } + return false; +} + +template +constexpr bool contains(E value) noexcept { + return contains_underlying(static_cast>(value)); +} + +} // namespace reflection + +#endif + +template +class static_str { + public: + constexpr explicit static_str(cname_ref str) noexcept : static_str{str.str_, std::make_integer_sequence{}} { + MAGIC_ENUM_ASSERT(str.size_ == N); + } + + constexpr explicit static_str(const char* const str) noexcept : static_str{str, std::make_integer_sequence{}} {} + + constexpr explicit static_str(string_view str) noexcept : static_str{str.data(), std::make_integer_sequence{}} { + MAGIC_ENUM_ASSERT(str.size() == N); + } + + constexpr const char_type* data() const noexcept { return chars_; } + + constexpr std::uint16_t size() const noexcept { return N; } + + constexpr string_view str() const noexcept { return string_view(data(), size()); } + + char_type chars_[static_cast(N) + 1]; + + private: + [[nodiscard]] static constexpr char_type to_char_type(char value) noexcept { + if constexpr (std::is_same_v) { + return value; + } else { + return static_cast(value); + } + } + + template + constexpr static_str(const char* str, std::integer_sequence) noexcept : chars_{to_char_type(str[J])..., char_type{}} {} + + template + constexpr static_str(string_view str, std::integer_sequence) noexcept : chars_{str[J]..., char_type{}} {} +}; + +template <> +class static_str<0> { + public: + constexpr static_str() noexcept = default; + + constexpr static_str(cname_ref) noexcept {} + + constexpr static_str(string_view) noexcept {} + + constexpr const char_type* data() const noexcept { return chars_; } + + constexpr std::uint16_t size() const noexcept { return 0; } + + constexpr string_view str() const noexcept { return string_view(data(), size()); } + + static constexpr char_type chars_[1] = {}; +}; + +template > +class case_insensitive { + static constexpr char_type to_lower(char_type c) noexcept { + return (c >= char_type{'A'} && c <= char_type{'Z'}) ? static_cast(c + (char_type{'a'} - char_type{'A'})) : c; + } + + public: + template + constexpr auto operator()(L lhs, R rhs) const noexcept -> std::enable_if_t, char_type> && std::is_same_v, char_type>, bool> { + return Op{}(to_lower(lhs), to_lower(rhs)); + } +}; + +constexpr std::size_t find(string_view str, char_type c) noexcept { +#if defined(__clang__) && __clang_major__ < 9 && defined(__GLIBCXX__) || defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__) +// https://stackoverflow.com/questions/56484834/constexpr-stdstring-viewfind-last-of-doesnt-work-on-clang-8-with-libstdc +// https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html + constexpr bool workaround = true; +#else + constexpr bool workaround = false; +#endif + + if constexpr (workaround) { + for (std::size_t i = 0; i < str.size(); ++i) { + if (str[i] == c) { + return i; + } + } + + return string_view::npos; + } else { + return str.find(c); + } +} + +template +inline constexpr bool is_default_predicate_v = std::is_same_v, std::equal_to> || std::is_same_v, std::equal_to<>>; + +template +inline constexpr bool is_nothrow_invocable_v = is_default_predicate_v || std::is_nothrow_invocable_r_v; + +template +constexpr bool cmp_equal(string_view lhs, string_view rhs, [[maybe_unused]] BinaryPredicate&& p) noexcept(is_nothrow_invocable_v) { +#if defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__) + // https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html + // https://developercommunity.visualstudio.com/content/problem/232218/c-constexpr-string-view.html + constexpr bool workaround = true; +#else + constexpr bool workaround = false; +#endif + + if constexpr (!is_default_predicate_v || workaround) { + if (lhs.size() != rhs.size()) { + return false; + } + + const auto size = lhs.size(); + for (std::size_t i = 0; i < size; ++i) { + if (!p(lhs[i], rhs[i])) { + return false; + } + } + + return true; + } else { + return lhs == rhs; + } +} + +template +constexpr bool cmp_less(L lhs, R rhs) noexcept { + static_assert(std::is_integral_v && std::is_integral_v, "magic_enum::detail::cmp_less requires integral type."); + + if constexpr (std::is_same_v && std::is_same_v) { + return static_cast(lhs) < static_cast(rhs); + } else if constexpr (std::is_same_v) { + return static_cast(lhs) < rhs; + } else if constexpr (std::is_same_v) { + return lhs < static_cast(rhs); + } else if constexpr (std::is_signed_v == std::is_signed_v) { + return lhs < rhs; + } else if constexpr (std::is_signed_v) { + using C = std::common_type_t, std::make_unsigned_t>; + return rhs > 0 && static_cast(lhs) < static_cast(rhs); + } else { + using C = std::common_type_t, std::make_unsigned_t>; + return lhs < 0 || static_cast(lhs) < static_cast(rhs); + } +} + +template +using make_unsigned_t = std::make_unsigned_t, unsigned char, T>>; + +template +constexpr T log2(T value) noexcept { + static_assert(std::is_integral_v, "magic_enum::detail::log2 requires integral type."); + + auto ret = T{0}; + for (; value > T{1}; value >>= T{1}, ++ret) {} + + return ret; +} + +#if defined(__cpp_lib_array_constexpr) && __cpp_lib_array_constexpr >= 201603L +# define MAGIC_ENUM_ARRAY_CONSTEXPR 1 +#else +template +constexpr std::array, N> to_array(T(&a)[N], std::index_sequence) noexcept { + return {{a[J]...}}; +} +#endif + +template +inline constexpr bool is_enum_v = std::is_enum_v && std::is_same_v>; + +template +constexpr auto MAGIC_ENUM_CALLING_CONVENTION n() noexcept { + static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); + + if constexpr (supported::value) { +#if defined(MAGIC_ENUM_DETAIL_USE_STD_REFLECTION) + auto name = reflection::type_name(); +#elif defined(MAGIC_ENUM_GET_TYPE_NAME_BUILTIN) + constexpr auto name_ptr = MAGIC_ENUM_GET_TYPE_NAME_BUILTIN(E); + constexpr auto name = name_ptr ? cname_ref{name_ptr, std::char_traits::length(name_ptr)} : cname_ref{}; +#elif defined(__clang__) + cname_ref name; + if constexpr (sizeof(__PRETTY_FUNCTION__) == sizeof(__FUNCTION__)) { + static_assert(always_false_v, "magic_enum::detail::n requires __PRETTY_FUNCTION__."); + return cname_ref{}; + } else { + name.size_ = sizeof(__PRETTY_FUNCTION__) - 36; + name.str_ = __PRETTY_FUNCTION__ + 34; + } +#elif defined(__GNUC__) + auto name = cname_ref{__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 1}; + if constexpr (sizeof(__PRETTY_FUNCTION__) == sizeof(__FUNCTION__)) { + static_assert(always_false_v, "magic_enum::detail::n requires __PRETTY_FUNCTION__."); + return cname_ref{}; + } else if (name.str_[name.size_ - 1] == ']') { + name.size_ -= 50; + name.str_ += 49; + } else { + name.size_ -= 40; + name.str_ += 37; + } +#elif defined(_MSC_VER) + // CLI/C++ workaround (see https://github.com/Neargye/magic_enum/issues/284). + cname_ref name; + name.str_ = __FUNCSIG__; + name.str_ += 40; + name.size_ += sizeof(__FUNCSIG__) - 57; +#else + auto name = cname_ref{}; +#endif +#if !defined(MAGIC_ENUM_DETAIL_USE_STD_REFLECTION) + std::size_t p = 0; + for (std::size_t i = name.size_; i > 0; --i) { + if (name.str_[i] == ':') { + p = i + 1; + break; + } + } + if (p > 0) { + name.size_ -= p; + name.str_ += p; + } +#endif + return name; + } else { + return cname_ref{}; // Unsupported compiler or Invalid customize. + } +} + +template +constexpr auto type_name() noexcept { + [[maybe_unused]] constexpr auto custom = customize::enum_type_name(); + static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); + if constexpr (custom.first == customize::detail::customize_tag::custom_tag) { + constexpr auto name = custom.second; + static_assert(!name.empty(), "magic_enum::customize requires not empty string."); + return static_str{name}; + } else if constexpr (custom.first == customize::detail::customize_tag::invalid_tag) { + return static_str<0>{}; + } else if constexpr (custom.first == customize::detail::customize_tag::default_tag) { + constexpr auto name = n(); + return static_str{name}; + } else { + static_assert(always_false_v, "magic_enum::customize invalid."); + } +} + +template +inline constexpr auto type_name_v = type_name(); + +template +constexpr auto MAGIC_ENUM_CALLING_CONVENTION n() noexcept { + static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); + + if constexpr (supported::value) { +#if defined(MAGIC_ENUM_DETAIL_USE_STD_REFLECTION) + auto name = reflection::enum_name(); +#elif defined(MAGIC_ENUM_GET_ENUM_NAME_BUILTIN) + constexpr auto name_ptr = MAGIC_ENUM_GET_ENUM_NAME_BUILTIN(V); + auto name = name_ptr ? cname_ref{name_ptr, std::char_traits::length(name_ptr)} : cname_ref{}; +#elif defined(__clang__) + cname_ref name; + if constexpr (sizeof(__PRETTY_FUNCTION__) == sizeof(__FUNCTION__)) { + static_assert(always_false_v, "magic_enum::detail::n requires __PRETTY_FUNCTION__."); + return cname_ref{}; + } else { + name.size_ = sizeof(__PRETTY_FUNCTION__) - 36; + name.str_ = __PRETTY_FUNCTION__ + 34; + } + if (name.size_ > 22 && name.str_[0] == '(' && name.str_[1] == 'a' && name.str_[10] == ' ' && name.str_[22] == ':') { + name.size_ -= 23; + name.str_ += 23; + } + if (name.str_[0] == '(' || name.str_[0] == '-' || (name.str_[0] >= '0' && name.str_[0] <= '9')) { + name = cname_ref{}; + } +#elif defined(__GNUC__) + auto name = cname_ref{__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 1}; + if constexpr (sizeof(__PRETTY_FUNCTION__) == sizeof(__FUNCTION__)) { + static_assert(always_false_v, "magic_enum::detail::n requires __PRETTY_FUNCTION__."); + return cname_ref{}; + } else if (name.str_[name.size_ - 1] == ']') { + name.size_ -= 55; + name.str_ += 54; + } else { + name.size_ -= 40; + name.str_ += 37; + } + if (name.str_[0] == '(') { + name = cname_ref{}; + } +#elif defined(_MSC_VER) + cname_ref name; + if ((__FUNCSIG__[5] == '_' && __FUNCSIG__[35] != '(') || (__FUNCSIG__[5] == 'c' && __FUNCSIG__[41] != '(')) { + // CLI/C++ workaround (see https://github.com/Neargye/magic_enum/issues/284). + name.str_ = __FUNCSIG__; + name.str_ += 35; + name.size_ = sizeof(__FUNCSIG__) - 52; + } +#else + auto name = cname_ref{}; +#endif +#if !defined(MAGIC_ENUM_DETAIL_USE_STD_REFLECTION) + std::size_t p = 0; + for (std::size_t i = name.size_; i > 0; --i) { + if (name.str_[i] == ':') { + p = i + 1; + break; + } + } + if (p > 0) { + name.size_ -= p; + name.str_ += p; + } +#endif + return name; + } else { + return cname_ref{}; // Unsupported compiler or Invalid customize. + } +} + +#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER < 1920 +# define MAGIC_ENUM_VS_2017_WORKAROUND 1 +#endif + +#if defined(MAGIC_ENUM_VS_2017_WORKAROUND) +template +constexpr auto MAGIC_ENUM_CALLING_CONVENTION n() noexcept { + static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); + +# if defined(MAGIC_ENUM_GET_ENUM_NAME_BUILTIN) + constexpr auto name_ptr = MAGIC_ENUM_GET_ENUM_NAME_BUILTIN(V); + auto name = name_ptr ? cname_ref{name_ptr, std::char_traits::length(name_ptr)} : cname_ref{}; +# else + // CLI/C++ workaround (see https://github.com/Neargye/magic_enum/issues/284). + cname_ref name; + name.str_ = __FUNCSIG__; + name.size_ = sizeof(__FUNCSIG__) - 1; + while (name.size_ > 0 && name.str_[name.size_ - 1] != '>') { + --name.size_; + } + if (name.size_ > 0) { + --name.size_; + } + std::size_t p = 0, depth = 0; + for (std::size_t i = name.size_; i > 0; --i) { + if (name.str_[i - 1] == '>') { + ++depth; + } else if (name.str_[i - 1] == '<' && depth > 0) { + --depth; + } else if (name.str_[i - 1] == ',' && depth == 0) { + p = i; + break; + } + } + if (p > 0) { + name.size_ -= p; + name.str_ += p; + } + if (name.str_[0] == '(' || name.str_[0] == '-' || (name.str_[0] >= '0' && name.str_[0] <= '9')) { + name = cname_ref{}; + } else { + for (std::size_t i = name.size_; i > 0; --i) { + if (name.str_[i - 1] == ':') { + name.size_ -= i; + name.str_ += i; + break; + } + } + } + return name; +# endif +} +#endif + +template +constexpr auto enum_name() noexcept { + [[maybe_unused]] constexpr auto custom = customize::enum_name(V); + static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); + if constexpr (custom.first == customize::detail::customize_tag::custom_tag) { +#if defined(MAGIC_ENUM_DETAIL_USE_STD_REFLECTION) + static_assert(reflection::contains(V), "magic_enum::customize::enum_name cannot add an undeclared enum value when standard reflection is enabled."); +#endif + constexpr auto name = custom.second; + static_assert(!name.empty(), "magic_enum::customize requires not empty string."); + return static_str{name}; + } else if constexpr (custom.first == customize::detail::customize_tag::invalid_tag) { + return static_str<0>{}; + } else if constexpr (custom.first == customize::detail::customize_tag::default_tag) { +#if defined(MAGIC_ENUM_VS_2017_WORKAROUND) + constexpr auto name = n(); +#else + constexpr auto name = n(); +#endif + constexpr auto prefix = prefix_length_v; + constexpr bool valid_prefix = valid_prefix_length(prefix, name.size_); + static_assert(valid_prefix, "magic_enum::customize requires valid prefix length."); + if constexpr (valid_prefix) { + return static_str{name.str_ + prefix}; + } else { + return static_str<0>{}; + } + } else { + static_assert(always_false_v, "magic_enum::customize invalid."); + } +} + +template +inline constexpr auto enum_name_v = enum_name(); + +// CWG1766: Values outside the range of the values of an enumeration +// https://reviews.llvm.org/D130058, https://reviews.llvm.org/D131307 +#if defined(__clang__) && __clang_major__ >= 16 +template +inline constexpr bool is_enum_constexpr_static_cast_valid = false; +template +inline constexpr bool is_enum_constexpr_static_cast_valid(V)>>> = true; +#else +template +inline constexpr bool is_enum_constexpr_static_cast_valid = true; +#endif + +template +constexpr bool is_valid() noexcept { + if constexpr (is_enum_constexpr_static_cast_valid) { + constexpr E v = static_cast(V); + [[maybe_unused]] constexpr auto custom = customize::enum_name(v); + static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); + if constexpr (custom.first == customize::detail::customize_tag::custom_tag) { + constexpr auto name = custom.second; + static_assert(!name.empty(), "magic_enum::customize requires not empty string."); + return name.size() != 0; + } else if constexpr (custom.first == customize::detail::customize_tag::default_tag) { +#if defined(MAGIC_ENUM_VS_2017_WORKAROUND) + return n().size_ != 0; +#else + return n().size_ != 0; +#endif + } else { + return false; + } + } else { + return false; + } +} + +enum class enum_subtype { + common, + flags +}; + +#if defined(MAGIC_ENUM_DETAIL_USE_STD_REFLECTION) + +namespace reflection { + +template > +constexpr bool value_valid(E value) noexcept { + if constexpr (S == enum_subtype::common) { + return true; + } else { + const auto integer = static_cast(value); + if constexpr (std::is_signed_v) { + if (integer <= 0) { + return false; + } + } + using V = make_unsigned_t; + const auto flag = static_cast(integer); + return flag != V{0} && (flag & (flag - V{1})) == V{0}; + } +} + +struct name_ref { + const char_type* data = nullptr; + std::size_t size = 0; + + [[nodiscard]] constexpr string_view view() const noexcept { return {data, size}; } +}; + +template +struct entry { + E value{}; + name_ref name{}; + std::size_t declaration_index = 0; +}; + +template +struct table { + std::array, N> entries{}; + std::size_t size = 0; + bool prefixes_valid = true; +}; + +template > +consteval auto canonical_data() noexcept { + table.size()> result{}; + constexpr auto prefix = prefix_length_v; + [[maybe_unused]] std::size_t declaration_index = 0; + + template for (constexpr auto enumerator : enumerators_v) { + constexpr E value = [:enumerator:]; + if constexpr (value_valid(value)) { + constexpr auto custom = customize::enum_name(value); + static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); + + if constexpr (custom.first == customize::detail::customize_tag::custom_tag) { + constexpr auto name = custom.second; + static_assert(!name.empty(), "magic_enum::customize requires not empty string."); + constexpr auto persistent_name = std::define_static_string(std::span{name.data(), name.size()}); + result.entries[result.size++] = entry{value, {persistent_name, name.size()}, declaration_index}; + } else if constexpr (custom.first == customize::detail::customize_tag::invalid_tag) { + // An invalid customization removes this declared enumerator from the public tables. + } else if constexpr (custom.first == customize::detail::customize_tag::default_tag) { + constexpr auto identifier = std::meta::identifier_of(enumerator); + if constexpr (valid_prefix_length(prefix, identifier.size())) { + if constexpr (std::is_same_v) { + constexpr auto persistent_name = std::define_static_string(identifier.substr(prefix)); + result.entries[result.size++] = entry{value, {persistent_name, identifier.size() - prefix}, declaration_index}; + } else { + constexpr auto converted_name = [=] { + std::array name{}; + for (std::size_t i = 0; i < name.size(); ++i) { + name[i] = static_cast(identifier[i + prefix]); + } + return name; + }(); + constexpr auto persistent_name = std::define_static_string(converted_name); + result.entries[result.size++] = entry{value, {persistent_name, converted_name.size()}, declaration_index}; + } + } else { + result.entries[result.size++] = entry{value, {}, declaration_index}; + } + } else { + static_assert(always_false_v, "magic_enum::customize invalid."); + } + } + ++declaration_index; + } + + const auto less = [](const auto& lhs, const auto& rhs) { + const auto lhs_value = static_cast(lhs.value); + const auto rhs_value = static_cast(rhs.value); + if (lhs_value != rhs_value) { + return lhs_value < rhs_value; + } + return lhs.declaration_index < rhs.declaration_index; + }; + const auto first = result.entries.begin(); + const auto last = first + result.size; + if (!std::is_sorted(first, last, less)) { + std::sort(first, last, less); + } + + // Valid reflected names are non-empty, so an empty name marks an invalid prefix on the retained declaration. + std::size_t unique_size = 0; + for (std::size_t i = 0; i < result.size; ++i) { + if (unique_size == 0 || !enum_value_equal(result.entries[unique_size - 1].value, result.entries[i].value)) { + result.prefixes_valid = result.prefixes_valid && result.entries[i].name.size != 0; + if (unique_size != i) { + result.entries[unique_size] = result.entries[i]; + } + ++unique_size; + } + } + result.size = unique_size; + return result; +} + +template +inline constexpr auto table_v = canonical_data(); + +template +consteval std::size_t table_size() noexcept { + static_assert(table_v.prefixes_valid, "magic_enum::customize requires valid prefix length."); + return table_v.size; +} + +} // namespace reflection + +#endif + +template > +constexpr U ualue(std::size_t i) noexcept { + if constexpr (S == enum_subtype::flags) { + using V = make_unsigned_t; + const auto shifted = V{1} << static_cast(static_cast(i) + O); + return static_cast(shifted); + } else { + return static_cast(static_cast(i) + O); + } +} + +template > +constexpr E value(std::size_t i) noexcept { + return static_cast(ualue(i)); +} + +template > +constexpr int reflected_min() noexcept { + if constexpr (S == enum_subtype::flags) { + return 0; + } else { + constexpr auto lhs = range_min::value; + constexpr auto rhs = (std::numeric_limits::min)(); + + if constexpr (cmp_less(rhs, lhs)) { + return lhs; + } else { + return rhs; + } + } +} + +template > +constexpr int reflected_max() noexcept { + if constexpr (S == enum_subtype::flags) { + return std::numeric_limits::digits - 1; + } else { + constexpr auto lhs = range_max::value; + constexpr auto rhs = (std::numeric_limits::max)(); + + if constexpr (cmp_less(lhs, rhs)) { + return lhs; + } else { + return rhs; + } + } +} + +#define MAGIC_ENUM_FOR_EACH_256(T) \ + T( 0)T( 1)T( 2)T( 3)T( 4)T( 5)T( 6)T( 7)T( 8)T( 9)T( 10)T( 11)T( 12)T( 13)T( 14)T( 15)T( 16)T( 17)T( 18)T( 19)T( 20)T( 21)T( 22)T( 23)T( 24)T( 25)T( 26)T( 27)T( 28)T( 29)T( 30)T( 31) \ + T( 32)T( 33)T( 34)T( 35)T( 36)T( 37)T( 38)T( 39)T( 40)T( 41)T( 42)T( 43)T( 44)T( 45)T( 46)T( 47)T( 48)T( 49)T( 50)T( 51)T( 52)T( 53)T( 54)T( 55)T( 56)T( 57)T( 58)T( 59)T( 60)T( 61)T( 62)T( 63) \ + T( 64)T( 65)T( 66)T( 67)T( 68)T( 69)T( 70)T( 71)T( 72)T( 73)T( 74)T( 75)T( 76)T( 77)T( 78)T( 79)T( 80)T( 81)T( 82)T( 83)T( 84)T( 85)T( 86)T( 87)T( 88)T( 89)T( 90)T( 91)T( 92)T( 93)T( 94)T( 95) \ + T( 96)T( 97)T( 98)T( 99)T(100)T(101)T(102)T(103)T(104)T(105)T(106)T(107)T(108)T(109)T(110)T(111)T(112)T(113)T(114)T(115)T(116)T(117)T(118)T(119)T(120)T(121)T(122)T(123)T(124)T(125)T(126)T(127) \ + T(128)T(129)T(130)T(131)T(132)T(133)T(134)T(135)T(136)T(137)T(138)T(139)T(140)T(141)T(142)T(143)T(144)T(145)T(146)T(147)T(148)T(149)T(150)T(151)T(152)T(153)T(154)T(155)T(156)T(157)T(158)T(159) \ + T(160)T(161)T(162)T(163)T(164)T(165)T(166)T(167)T(168)T(169)T(170)T(171)T(172)T(173)T(174)T(175)T(176)T(177)T(178)T(179)T(180)T(181)T(182)T(183)T(184)T(185)T(186)T(187)T(188)T(189)T(190)T(191) \ + T(192)T(193)T(194)T(195)T(196)T(197)T(198)T(199)T(200)T(201)T(202)T(203)T(204)T(205)T(206)T(207)T(208)T(209)T(210)T(211)T(212)T(213)T(214)T(215)T(216)T(217)T(218)T(219)T(220)T(221)T(222)T(223) \ + T(224)T(225)T(226)T(227)T(228)T(229)T(230)T(231)T(232)T(233)T(234)T(235)T(236)T(237)T(238)T(239)T(240)T(241)T(242)T(243)T(244)T(245)T(246)T(247)T(248)T(249)T(250)T(251)T(252)T(253)T(254)T(255) + +template +struct valid_count_t { + std::uint16_t count = 0; + std::uint16_t offsets[N] = {}; + + constexpr void set(std::size_t i) noexcept { + offsets[count++] = static_cast(i); + } +}; + +template +constexpr void valid_count(valid_count_t& vc) noexcept { +#define MAGIC_ENUM_V(O) \ + if constexpr ((J + O) < Size) { \ + if constexpr (is_valid(J + O)>()) { \ + vc.set(J + O); \ + } \ + } + + MAGIC_ENUM_FOR_EACH_256(MAGIC_ENUM_V) + + if constexpr ((J + 256) < Size) { + valid_count(vc); + } +#undef MAGIC_ENUM_V +} + +template +constexpr auto valid_count() noexcept { + valid_count_t vc; + valid_count(vc); + return vc; +} + +template +constexpr auto values() noexcept { + constexpr auto vc = valid_count(); + static_assert(vc.count <= Size); + + if constexpr (vc.count > 0) { +#if defined(MAGIC_ENUM_ARRAY_CONSTEXPR) + std::array values = {}; +#else + E values[vc.count] = {}; +#endif + if constexpr (vc.count == Size) { + for (std::size_t i = 0; i < vc.count; ++i) { + values[i] = value(i); + } + } else { + for (std::size_t i = 0; i < vc.count; ++i) { + values[i] = value(vc.offsets[i]); + } + } +#if defined(MAGIC_ENUM_ARRAY_CONSTEXPR) + return values; +#else + return to_array(values, std::make_index_sequence{}); +#endif + } else { + return std::array{}; + } +} + +template > +constexpr auto values() noexcept { +#if defined(MAGIC_ENUM_DETAIL_USE_STD_REFLECTION) + std::array()> values{}; + for (std::size_t i = 0; i < values.size(); ++i) { + values[i] = reflection::table_v.entries[i].value; + } + return values; +#else + constexpr auto min = reflected_min(); + constexpr auto max = reflected_max(); + constexpr bool valid_range = min <= max; + static_assert(valid_range, "magic_enum::enum_range requires min <= max."); + + if constexpr (valid_range) { + constexpr auto range_size = max - min + 1; + constexpr bool valid_size = range_size <= (std::numeric_limits::max)(); + static_assert(valid_size, "magic_enum::enum_range requires valid size."); + + if constexpr (valid_size) { + return values(); + } else { + return std::array{}; + } + } else { + return std::array{}; + } +#endif +} + +template +constexpr enum_subtype subtype() noexcept { + if constexpr (std::is_enum_v) { + if constexpr (has_is_flags::value) { + return customize::enum_range::is_flags ? enum_subtype::flags : enum_subtype::common; + } + } + return enum_subtype::common; +} + +template > +inline constexpr enum_subtype subtype_v = subtype(); + +template +inline constexpr auto values_v = values(); + +template > +using values_t = decltype((values_v)); + +#if defined(MAGIC_ENUM_DETAIL_USE_STD_REFLECTION) + +template +inline constexpr auto count_v = reflection::table_size(); + +template > +inline constexpr auto min_v = (count_v > 0) ? static_cast(reflection::table_v.entries.front().value) : U{0}; + +template > +inline constexpr auto max_v = (count_v > 0) ? static_cast(reflection::table_v.entries[count_v - 1].value) : U{0}; + +#else + +template +inline constexpr auto count_v = values_v.size(); + +template > +inline constexpr auto min_v = (count_v > 0) ? static_cast(values_v.front()) : U{0}; + +template > +inline constexpr auto max_v = (count_v > 0) ? static_cast(values_v.back()) : U{0}; + +#endif + +#if defined(MAGIC_ENUM_DETAIL_USE_STD_REFLECTION) + +template +constexpr auto names() noexcept { + std::array> names{}; + for (std::size_t i = 0; i < names.size(); ++i) { + names[i] = reflection::table_v.entries[i].name.view(); + } + return names; +} + +template +inline constexpr auto names_v = names(); + +#else + +template +constexpr auto names(std::index_sequence) noexcept { + constexpr auto names = std::array{{enum_name_v[J]>.str()...}}; + return names; +} + +template +inline constexpr auto names_v = names(std::make_index_sequence>{}); + +#endif + +template > +using names_t = decltype((names_v)); + +#if defined(MAGIC_ENUM_DETAIL_USE_STD_REFLECTION) + +template +constexpr auto entries() noexcept { + std::array, count_v> entries{}; + for (std::size_t i = 0; i < entries.size(); ++i) { + entries[i] = {reflection::table_v.entries[i].value, reflection::table_v.entries[i].name.view()}; + } + return entries; +} + +template +inline constexpr auto entries_v = entries(); + +#else + +template +constexpr auto entries(std::index_sequence) noexcept { + constexpr auto entries = std::array, sizeof...(J)>{{{values_v[J], enum_name_v[J]>.str()}...}}; + return entries; +} + +template +inline constexpr auto entries_v = entries(std::make_index_sequence>{}); + +#endif + +template > +using entries_t = decltype((entries_v)); + +template > +constexpr bool is_sparse() noexcept { + if constexpr (count_v == 0) { + return false; +#if defined(MAGIC_ENUM_DETAIL_USE_STD_REFLECTION) + } else if constexpr (S == enum_subtype::common) { + if constexpr (cmp_less(min_v, (std::numeric_limits::min)()) || cmp_less((std::numeric_limits::max)(), max_v)) { + return true; + } else { + for (std::size_t i = 1; i < count_v; ++i) { + if (static_cast(reflection::table_v.entries[i - 1].value) + U{1} != static_cast(reflection::table_v.entries[i].value)) { + return true; + } + } + return false; + } +#endif + } else if constexpr (S == enum_subtype::flags) { + using V = make_unsigned_t; + constexpr auto max = log2(static_cast(max_v)); + constexpr auto min = log2(static_cast(min_v)); + constexpr auto range_size = max - min + 1; + + return range_size != count_v; + } else { + constexpr auto max = max_v; + constexpr auto min = min_v; + constexpr auto range_size = max - min + 1; + + return range_size != count_v; + } +} + +template > +inline constexpr bool is_sparse_v = is_sparse(); + +#if defined(MAGIC_ENUM_NO_CHECK_REFLECTED_ENUM) +template +struct is_reflected : std::true_type {}; +#else +template >> +struct is_reflected : std::false_type {}; + +template +struct is_reflected : std::bool_constant, S> != 0> {}; +#endif + +template +inline constexpr bool is_reflected_v = is_reflected::value; + +template +struct enable_if_enum {}; + +template +struct enable_if_enum { + using type = R; + static_assert(supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); +}; + +template , typename D = std::decay_t> +using enable_if_t = typename enable_if_enum && std::is_invocable_r_v, R>::type; + +template >, int> = 0> +using enum_concept = T; + +template > +struct is_scoped_enum : std::false_type {}; + +template +struct is_scoped_enum : std::bool_constant>> {}; + +template > +struct is_unscoped_enum : std::false_type {}; + +template +struct is_unscoped_enum : std::bool_constant>> {}; + +template > +struct is_flags_enum : std::false_type {}; + +template +struct is_flags_enum : std::bool_constant == enum_subtype::flags> {}; + +template >> +struct underlying_type {}; + +template +struct underlying_type : std::underlying_type> {}; + +template +constexpr decltype(auto) invoke_constant(F&& f, std::integral_constant&& value) noexcept(std::is_nothrow_invocable_v>) { + if constexpr (std::is_member_function_pointer_v>) { + return (std::move(value).*f)(); + } else if constexpr (std::is_member_object_pointer_v>) { + return std::move(value).*f; + } else { + return std::forward(f)(std::move(value)); + } +} + +#if defined(MAGIC_ENUM_ENABLE_HASH) || defined(MAGIC_ENUM_ENABLE_HASH_SWITCH) + +template +struct constexpr_hash_t; + +template +struct constexpr_hash_t>> { + using U = typename underlying_type::type; + + constexpr auto operator()(Value value) const noexcept { + return operator()(static_cast(value)); + } + + constexpr auto operator()(U value) const noexcept { + if constexpr (std::is_same_v) { // bool special case + return static_cast(value); + } else { + return value; + } + } + + using secondary_hash = constexpr_hash_t; +}; + +template +struct constexpr_hash_t>> { + static constexpr std::uint32_t crc_table[256] { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, + 0x0edb8832L, 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L, + 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, + 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, + 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, + 0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL, + 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, + 0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L, + 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, + 0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L, + 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, + 0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L, + 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, + 0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L, + 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, + 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, + 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, + 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L, + 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, + 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL, + 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, + 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L, + 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, + 0xa00ae278L, 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL, + 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL, + 0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL + }; + constexpr std::uint32_t operator()(string_view value) const noexcept { + auto crc = static_cast(0xffffffffL); + for (const auto c : value) { + crc = (crc >> 8) ^ crc_table[(crc ^ static_cast(c)) & 0xff]; + } + return crc ^ 0xffffffffL; + } + + struct secondary_hash { + constexpr std::uint32_t operator()(string_view value) const noexcept { + std::uint64_t acc = 2166136261ULL; + for (const auto c : value) { + acc = ((acc ^ static_cast(c)) * 16777619ULL) & (std::numeric_limits::max)(); + } + return static_cast(acc); + } + }; +}; + +template +inline constexpr Hash hash_v{}; + +template +constexpr auto calculate_cases(std::size_t Page) noexcept { + constexpr const auto& values = *GlobValues; + constexpr std::size_t size = values.size(); + + using switch_t = std::invoke_result_t::value_type>; + static_assert(std::is_integral_v && !std::is_same_v); + const std::size_t values_to = (std::min)(static_cast(256), size - Page); + + std::array result{}; + auto fill = result.begin(); + { + auto first = values.begin() + static_cast(Page); + auto last = values.begin() + static_cast(Page + values_to); + while (first != last) { + *fill++ = hash_v(*first++); + } + } + + auto value = (std::numeric_limits::min)(); + while (fill != result.end()) { + bool used = false; + for (std::size_t i = 0; i < values_to; ++i) { + if (result[i] == value) { + used = true; + break; + } + } + if (!used) { + *fill++ = value; + } + value = value == (std::numeric_limits::max)() ? (std::numeric_limits::min)() : static_cast(value + 1); + } + + return result; +} + +template +constexpr R invoke_r(F&& f, Args&&... args) noexcept(std::is_nothrow_invocable_r_v) { + if constexpr (std::is_member_pointer_v>) { + return static_cast(detail::invoke_constant(std::forward(f), std::forward(args)...)); + } else if constexpr (std::is_void_v) { + std::forward(f)(std::forward(args)...); + } else { + return static_cast(std::forward(f)(std::forward(args)...)); + } +} + +enum class case_call_t { + index, + value +}; + +template +inline constexpr auto default_result_type_lambda = []() noexcept(std::is_nothrow_default_constructible_v) { return T{}; }; + +template <> +inline constexpr auto default_result_type_lambda = []() noexcept {}; + +template +constexpr void hash_heap_sift_down(std::array& values, std::size_t root, std::size_t end) noexcept { + while (root < end / 2) { + auto child = root * 2 + 1; + if (child + 1 < end && values[child] < values[child + 1]) { + ++child; + } + if (!(values[root] < values[child])) { + return; + } + auto tmp = values[root]; + values[root] = values[child]; + values[child] = tmp; + root = child; + } +} + +template +constexpr void hash_heap_sort(std::array& values) noexcept { + for (auto root = N / 2; root > 0; --root) { + hash_heap_sift_down(values, root - 1, N); + } + for (auto end = N; end > 1; --end) { + auto tmp = values[0]; + values[0] = values[end - 1]; + values[end - 1] = tmp; + hash_heap_sift_down(values, 0, end - 1); + } +} + +template +constexpr bool has_unique_hashes() noexcept { + using value_t = std::decay_t; + using hash_value_t = std::invoke_result_t; + if constexpr (Arr->size() > 1) { + auto previous = hash_v((*Arr)[0]); + bool strictly_increasing = true; + for (std::size_t i = 1; i < Arr->size(); ++i) { + const auto current = hash_v((*Arr)[i]); + if (!(previous < current)) { + if (previous == current) { + return false; + } + strictly_increasing = false; + break; + } + previous = current; + } + if (strictly_increasing) { + return true; + } + } + + std::arraysize()> hashes{}; + if constexpr (Arr->size() <= 32) { + std::size_t size = 0; + for (auto elem : *Arr) { + hashes[size] = hash_v(elem); + for (auto i = size++; i > 0; --i) { + if (hashes[i] < hashes[i - 1]) { + auto tmp = hashes[i]; + hashes[i] = hashes[i - 1]; + hashes[i - 1] = tmp; + } else if (hashes[i] == hashes[i - 1]) { + return false; + } else { + break; + } + } + } + } else { + std::size_t size = 0; + for (auto elem : *Arr) { + hashes[size++] = hash_v(elem); + } + hash_heap_sort(hashes); + for (std::size_t i = 1; i < hashes.size(); ++i) { + if (hashes[i - 1] == hashes[i]) { + return false; + } + } + } + return true; +} + +template +inline constexpr bool has_unique_hashes_v = has_unique_hashes(); + +template +inline constexpr bool has_usable_hash_v = has_unique_hashes_v || has_unique_hashes_v; + +#define MAGIC_ENUM_CASE(val) \ + case cases[val]: \ + if constexpr ((val) + Page < size) { \ + if (!pred(values[val + Page], searched)) { \ + break; \ + } \ + if constexpr (CallValue == case_call_t::index) { \ + if constexpr (std::is_invocable_r_v>) { \ + return detail::invoke_r(std::forward(lambda), std::integral_constant{}); \ + } else if constexpr (std::is_invocable_v>) { \ + MAGIC_ENUM_ASSERT(false && "magic_enum::detail::hash_switch wrong result type."); \ + } \ + } else if constexpr (CallValue == case_call_t::value) { \ + if constexpr (std::is_invocable_r_v>) { \ + return detail::invoke_r(std::forward(lambda), enum_constant{}); \ + } else if constexpr (std::is_invocable_v>) { \ + MAGIC_ENUM_ASSERT(false && "magic_enum::detail::hash_switch wrong result type."); \ + } \ + } \ + break; \ + } else [[fallthrough]]; + +template +constexpr decltype(auto) hash_switch_page( + Lambda&& lambda, + Searched searched, + SearchedHash searched_hash, + ResultGetterType&& def, + BinaryPredicate&& pred) { + using result_t = std::invoke_result_t; + constexpr const auto& values = *GlobValues; + constexpr std::size_t size = values.size(); + constexpr auto cases = calculate_cases(Page); + + switch (searched_hash) { + MAGIC_ENUM_FOR_EACH_256(MAGIC_ENUM_CASE) + default: + if constexpr (size > 256 + Page) { + return hash_switch_page(std::forward(lambda), searched, searched_hash, std::forward(def), std::forward(pred)); + } + break; + } + return def(); +} + +template ::value_type>, + typename BinaryPredicate = std::equal_to<>, + typename Lambda, + typename Searched, + typename ResultGetterType> +constexpr decltype(auto) hash_switch( + Lambda&& lambda, + Searched searched, + ResultGetterType&& def, + BinaryPredicate&& pred = {}) { + using hash_t = std::conditional_t, Hash, typename Hash::secondary_hash>; + static_assert(has_unique_hashes_v, "magic_enum::detail::hash_switch duplicated hash found, please report it: https://github.com/Neargye/magic_enum/issues."); + const auto searched_hash = hash_v(searched); + return hash_switch_page(std::forward(lambda), searched, searched_hash, std::forward(def), std::forward(pred)); +} + +// values_v is unique, and constexpr_hash_t preserves its underlying values. +template , + typename Lambda, + typename Searched, + typename ResultGetterType> +constexpr decltype(auto) hash_switch_values( + Lambda&& lambda, + Searched searched, + ResultGetterType&& def, + BinaryPredicate&& pred = {}) { + using D = std::decay_t; + using hash_t = constexpr_hash_t; + static_assert(std::is_enum_v); + const auto searched_hash = hash_v(searched); + return hash_switch_page<&values_v, CallValue, 0, hash_t>(std::forward(lambda), searched, searched_hash, std::forward(def), std::forward(pred)); +} + +#undef MAGIC_ENUM_CASE + +#endif + +} // namespace magic_enum::detail + +// Checks is magic_enum supported compiler. +inline constexpr bool is_magic_enum_supported = detail::supported::value; + +template +using Enum = detail::enum_concept; + +// Identifies unscoped enum types. +template +struct is_unscoped_enum : detail::is_unscoped_enum {}; + +template +inline constexpr bool is_unscoped_enum_v = is_unscoped_enum::value; + +// Identifies scoped enum types. +template +struct is_scoped_enum : detail::is_scoped_enum {}; + +template +inline constexpr bool is_scoped_enum_v = is_scoped_enum::value; + +// Identifies flag enum types (i.e., enum_range::is_flags == true). +template +struct is_flags_enum : detail::is_flags_enum {}; + +template +inline constexpr bool is_flags_v = is_flags_enum::value; + +// If T is a complete enumeration type, provides a member typedef type that names the underlying type of T. +// Otherwise, if T is not an enumeration type, there is no member type. Otherwise (T is an incomplete enumeration type), the program is ill-formed. +template +struct underlying_type : detail::underlying_type {}; + +template +using underlying_type_t = typename underlying_type::type; + +template +using enum_constant = detail::enum_constant; + +// Returns type name of enum. +template +[[nodiscard]] constexpr auto enum_type_name() noexcept -> detail::enable_if_t { + constexpr string_view name = detail::type_name_v>.str(); + static_assert(!name.empty(), "magic_enum::enum_type_name enum type does not have a name."); + + return name; +} + +// Returns number of enum values. +template > +[[nodiscard]] constexpr auto enum_count() noexcept -> detail::enable_if_t { + return detail::count_v, S>; +} + +// Returns enum value at specified index. +// No bounds checking is performed: the behavior is undefined if index >= number of enum values. +template > +[[nodiscard]] constexpr auto enum_value(std::size_t index) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + using U = underlying_type_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::is_sparse_v) { + return MAGIC_ENUM_ASSERT(index < detail::count_v), detail::values_v[index]; + } else if constexpr (S == detail::enum_subtype::flags) { + using V = detail::make_unsigned_t; + constexpr auto min = detail::log2(static_cast(detail::min_v)); + + return MAGIC_ENUM_ASSERT(index < detail::count_v), detail::value(index); + } else { + constexpr auto min = detail::min_v; + + return MAGIC_ENUM_ASSERT(index < detail::count_v), detail::value(index); + } +} + +// Returns enum value at specified index. +template > +[[nodiscard]] constexpr auto enum_value() noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + static_assert(J < detail::count_v, "magic_enum::enum_value out of range."); + + return enum_value(J); +} + +// Returns std::array with enum values, sorted by enum value. +template > +[[nodiscard]] constexpr auto enum_values() noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + return detail::values_v; +} + +// Returns integer value from enum value. +template +[[nodiscard]] constexpr auto enum_integer(E value) noexcept -> underlying_type_t { + return static_cast>(value); +} + +// Returns underlying value from enum value. +template +[[nodiscard]] constexpr auto enum_underlying(E value) noexcept -> underlying_type_t { + return static_cast>(value); +} + +// Returns index in enum values from enum value. +// Returns optional with index. +template > +[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + using U = underlying_type_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::is_sparse_v || (S == detail::enum_subtype::flags)) { +#if defined(MAGIC_ENUM_ENABLE_HASH) + return detail::hash_switch_values( + [](std::size_t i) { return optional{i}; }, + value, + detail::default_result_type_lambda>, + [](D lhs, D rhs) { return detail::enum_value_equal(lhs, rhs); }); +#else + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (detail::enum_value_equal(enum_value(i), value)) { + return i; + } + } + return {}; // Invalid value or out of range. +#endif + } else { + const auto v = static_cast(value); + if (v >= detail::min_v && v <= detail::max_v) { + return static_cast(v - detail::min_v); + } + return {}; // Invalid value or out of range. + } +} + +// Returns index in enum values from enum value. +// Returns optional with index. +template +[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_t> { + return enum_index, S>(value); +} + +// Returns index in enum values from compile-time enum value. +template > +[[nodiscard]] constexpr auto enum_index() noexcept -> detail::enable_if_t { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + constexpr auto index = enum_index(V); + static_assert(index, "magic_enum::enum_index enum value has no index."); + + return *index; +} + +// Returns name from compile-time enum value. +// Compiles faster than enum_name(value) and is not restricted by enum_range. +template +[[nodiscard]] constexpr auto enum_name() noexcept -> detail::enable_if_t { + constexpr string_view name = detail::enum_name_v, V>.str(); + static_assert(!name.empty(), "magic_enum::enum_name enum value does not have a name."); + + return name; +} + +// Returns name from enum value. +// If enum value does not have name or value out of range, returns empty string. +template > +[[nodiscard]] constexpr auto enum_name(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if (const auto i = enum_index(value)) { + return detail::names_v[*i]; + } + return detail::static_str<0>{}.str(); +} + +// Returns name from enum value. +// If enum value does not have name or value out of range, returns empty string. +template +[[nodiscard]] constexpr auto enum_name(E value) noexcept -> detail::enable_if_t { + return enum_name, S>(value); +} + +// Returns std::array with names, sorted by enum value. +template > +[[nodiscard]] constexpr auto enum_names() noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + return detail::names_v; +} + +// Returns std::array with pairs (value, name), sorted by enum value. +template > +[[nodiscard]] constexpr auto enum_entries() noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + return detail::entries_v; +} + +// Allows you to write magic_enum::enum_cast("bar", magic_enum::case_insensitive); +inline constexpr auto case_insensitive = detail::case_insensitive<>{}; + +// Returns enum value from integer value. +// Returns optional with enum value. +template > +[[nodiscard]] constexpr auto enum_cast(underlying_type_t value) noexcept -> detail::enable_if_t>> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::is_sparse_v || (S == detail::enum_subtype::flags)) { +#if defined(MAGIC_ENUM_ENABLE_HASH) + return detail::hash_switch_values( + [](D v) { return optional{v}; }, + value, + detail::default_result_type_lambda>, + [](D lhs, underlying_type_t rhs) { return detail::enum_value_equal(lhs, rhs); }); +#else + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (detail::enum_value_equal(enum_value(i), value)) { + return static_cast(value); + } + } + return {}; // Invalid value or out of range. +#endif + } else { + if (value >= detail::min_v && value <= detail::max_v) { + return static_cast(value); + } + return {}; // Invalid value or out of range. + } +} + +// Returns enum value from name. +// Returns optional with enum value. +template , typename BinaryPredicate = std::equal_to<>> +[[nodiscard]] constexpr auto enum_cast(string_view value, [[maybe_unused]] BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable_v) -> detail::enable_if_t>, BinaryPredicate> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + +#if defined(MAGIC_ENUM_ENABLE_HASH) + using hash_t = detail::constexpr_hash_t; + if constexpr (detail::is_default_predicate_v && detail::has_usable_hash_v<&detail::names_v, hash_t>) { + return detail::hash_switch<&detail::names_v, detail::case_call_t::index>( + [](std::size_t i) { return optional{detail::values_v[i]}; }, + value, + detail::default_result_type_lambda>, + [&p](string_view lhs, string_view rhs) { return detail::cmp_equal(lhs, rhs, p); }); + } else { +#endif + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (detail::cmp_equal(value, detail::names_v[i], p)) { + return enum_value(i); + } + } + return {}; // Invalid value or out of range. +#if defined(MAGIC_ENUM_ENABLE_HASH) + } +#endif +} + +// Returns true if enum contains specified value. +template > +[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + using U = underlying_type_t; + + return static_cast(enum_cast(static_cast(value))); +} + +// Returns true if enum contains specified value. +template +[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_t { + return enum_contains, S>(value); +} + +// Returns true if enum contains specified integer value. +template > +[[nodiscard]] constexpr auto enum_contains(underlying_type_t value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + + return static_cast(enum_cast(value)); +} + +// Returns true if enum contains enumerator with specified name. +template , typename BinaryPredicate = std::equal_to<>> +[[nodiscard]] constexpr auto enum_contains(string_view value, BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable_v) -> detail::enable_if_t { + using D = std::decay_t; + + return static_cast(enum_cast(value, p)); +} + +// Returns true if enum integer value can be reflected. +template > +[[nodiscard]] constexpr auto enum_reflected(underlying_type_t value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + +#if defined(MAGIC_ENUM_DETAIL_USE_STD_REFLECTION) + return detail::reflection::contains_underlying(value) && detail::reflection::value_valid(static_cast(value)); +#else + using T = underlying_type_t; + if constexpr (!detail::is_reflected_v) { + return false; + } else { + constexpr auto min = detail::reflected_min(); + constexpr auto max = detail::reflected_max(); + + if constexpr (S == detail::enum_subtype::common) { + return !detail::cmp_less(value, min) && !detail::cmp_less(max, value); + } else { + if (value <= T{0}) { + return false; + } + + using U = detail::make_unsigned_t; + const auto v = static_cast(value); + if ((v & (v - U{1})) != U{0}) { + return false; + } + + const auto bit = detail::log2(v); + return !detail::cmp_less(bit, min) && !detail::cmp_less(max, bit); + } + } +#endif +} + +// Returns true if enum value can be reflected. +template > +[[nodiscard]] constexpr auto enum_reflected(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + + return enum_reflected(static_cast>(value)); +} + +// Returns true if enum value can be reflected. +template +[[nodiscard]] constexpr auto enum_reflected(E value) noexcept -> detail::enable_if_t { + return enum_reflected, S>(value); +} + +template +inline constexpr auto as_flags = AsFlags ? detail::enum_subtype::flags : detail::enum_subtype::common; + +template +inline constexpr auto as_common = AsCommon ? detail::enum_subtype::common : detail::enum_subtype::flags; + +namespace bitwise_operators { + +template = 0> +constexpr E operator~(E rhs) noexcept { + using U = underlying_type_t; + + if constexpr (std::is_same_v) { + return static_cast(!static_cast(rhs)); + } else { + return static_cast(~static_cast(rhs)); + } +} + +template = 0> +constexpr E operator|(E lhs, E rhs) noexcept { + return static_cast(static_cast>(lhs) | static_cast>(rhs)); +} + +template = 0> +constexpr E operator&(E lhs, E rhs) noexcept { + return static_cast(static_cast>(lhs) & static_cast>(rhs)); +} + +template = 0> +constexpr E operator^(E lhs, E rhs) noexcept { + return static_cast(static_cast>(lhs) ^ static_cast>(rhs)); +} + +template = 0> +constexpr E& operator|=(E& lhs, E rhs) noexcept { + return lhs = (lhs | rhs); +} + +template = 0> +constexpr E& operator&=(E& lhs, E rhs) noexcept { + return lhs = (lhs & rhs); +} + +template = 0> +constexpr E& operator^=(E& lhs, E rhs) noexcept { + return lhs = (lhs ^ rhs); +} + +} // namespace magic_enum::bitwise_operators + +} // namespace magic_enum + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +#undef MAGIC_ENUM_GET_ENUM_NAME_BUILTIN +#undef MAGIC_ENUM_GET_TYPE_NAME_BUILTIN +#undef MAGIC_ENUM_CALLING_CONVENTION +#undef MAGIC_ENUM_VS_2017_WORKAROUND +#undef MAGIC_ENUM_ARRAY_CONSTEXPR +#undef MAGIC_ENUM_FOR_EACH_256 +#undef MAGIC_ENUM_DETAIL_USE_STD_REFLECTION + +#endif // NEARGYE_MAGIC_ENUM_HPP diff --git a/build-config/magicenum/include/magic_enum/magic_enum_all.hpp b/build-config/magicenum/include/magic_enum/magic_enum_all.hpp new file mode 100644 index 0000000..e771e11 --- /dev/null +++ b/build-config/magicenum/include/magic_enum/magic_enum_all.hpp @@ -0,0 +1,44 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.8 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2026 Daniil Goncharov . +// +// 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 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. + +#ifndef NEARGYE_MAGIC_ENUM_ALL_HPP +#define NEARGYE_MAGIC_ENUM_ALL_HPP + +#include "magic_enum.hpp" +#include "magic_enum_containers.hpp" +#include "magic_enum_flags.hpp" +#include "magic_enum_format.hpp" +#include "magic_enum_fuse.hpp" +#include "magic_enum_iostream.hpp" +#include "magic_enum_switch.hpp" +#include "magic_enum_utility.hpp" + +#endif // NEARGYE_MAGIC_ENUM_ALL_HPP diff --git a/build-config/magicenum/include/magic_enum/magic_enum_containers.hpp b/build-config/magicenum/include/magic_enum/magic_enum_containers.hpp new file mode 100644 index 0000000..f55a69a --- /dev/null +++ b/build-config/magicenum/include/magic_enum/magic_enum_containers.hpp @@ -0,0 +1,1457 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.8 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2026 Daniil Goncharov . +// Copyright (c) 2022 - 2023 Bela Schaum . +// +// 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 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. + +#ifndef NEARGYE_MAGIC_ENUM_CONTAINERS_HPP +#define NEARGYE_MAGIC_ENUM_CONTAINERS_HPP + +#include "magic_enum.hpp" + +#ifndef MAGIC_ENUM_USE_STD_MODULE +# include +# include +# include +#endif + +#if !defined(MAGIC_ENUM_USE_STD_MODULE) && __has_include() && (__cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)) +# include +#endif + +#if (!defined(__cpp_lib_bitops) || (__cpp_lib_bitops < 201907L)) && defined(_MSC_VER) && !defined(__clang__) +# include +# pragma intrinsic(_BitScanForward) +# pragma intrinsic(_BitScanReverse) +# ifdef _WIN64 +# pragma intrinsic(_BitScanForward64) +# pragma intrinsic(_BitScanReverse64) +# endif +#endif + +#if !defined(MAGIC_ENUM_NO_EXCEPTION) && (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) +# ifndef MAGIC_ENUM_USE_STD_MODULE +# include +# endif +# define MAGIC_ENUM_CONTAINERS_THROW(...) throw (__VA_ARGS__) +#else +# ifndef MAGIC_ENUM_USE_STD_MODULE +# include +# endif +# define MAGIC_ENUM_CONTAINERS_THROW(...) std::abort() +#endif + +namespace magic_enum::containers { + +namespace detail { + +template +inline constexpr bool is_transparent_v{}; + +template +inline constexpr bool is_transparent_v>{true}; + +template , typename T1, typename T2> +constexpr bool equal(T1&& t1, T2&& t2, Eq&& eq = {}) { + auto first1 = t1.begin(); + auto last1 = t1.end(); + auto first2 = t2.begin(); + auto last2 = t2.end(); + + for (; first1 != last1; ++first1, ++first2) { + if (first2 == last2 || !eq(*first1, *first2)) { + return false; + } + } + return first2 == last2; +} + +template , typename T1, typename T2> +constexpr bool lexicographical_compare(T1&& t1, T2&& t2, Cmp&& cmp = {}) { + auto first1 = t1.begin(); + auto last1 = t1.end(); + auto first2 = t2.begin(); + auto last2 = t2.end(); + + // copied from std::lexicographical_compare + for (; (first1 != last1) && (first2 != last2); ++first1, (void)++first2) { + if (cmp(*first1, *first2)) { + return true; + } + if (cmp(*first2, *first1)) { + return false; + } + } + return (first1 == last1) && (first2 != last2); +} + +template +constexpr std::size_t popcount(T x) noexcept { +#if defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L + return static_cast(std::popcount(x)); +#else + std::size_t c = 0; + while (x > 0) { + x &= x - 1; + ++c; + } + return c; +#endif +} + +namespace impl { + +template , typename ForwardIt, typename E> +constexpr ForwardIt lower_bound(ForwardIt first, ForwardIt last, E&& e, Cmp&& comp = {}) { + auto count = std::distance(first, last); + while (count > 0) { + auto it = first; + auto step = count / 2; + std::advance(it, step); + if (comp(*it, e)) { + first = ++it; + count -= step + 1; + } else { + count = step; + } + } + return first; +} + +} // namespace impl + +template , typename BidirIt, typename E> +constexpr BidirIt upper_bound(BidirIt begin, BidirIt end, E&& e, Cmp&& comp = {}) { + return impl::lower_bound(std::make_reverse_iterator(end), std::make_reverse_iterator(begin), e, [&comp](auto&& lhs, auto&& rhs) { return comp(rhs, lhs); }).base(); +} + +template , typename BidirIt, typename E> +constexpr auto equal_range(BidirIt begin, BidirIt end, E&& e, Cmp&& comp = {}) { + const auto first = impl::lower_bound(begin, end, e, comp); + return std::pair{first, detail::upper_bound(first, end, e, comp)}; +} + +template , typename = void> +class indexing { + [[nodiscard]] static constexpr auto get_indices() noexcept { + // reverse result index mapping + std::array()> rev_res{}; + + // std::iota + for (std::size_t i = 0; i < enum_count(); ++i) { + rev_res[i] = i; + } + + constexpr auto orig_values = enum_values(); + constexpr Cmp cmp{}; + + // ~std::sort + for (std::size_t i = 0; i < enum_count(); ++i) { + for (std::size_t j = i + 1; j < enum_count(); ++j) { + if (cmp(orig_values[rev_res[j]], orig_values[rev_res[i]])) { + auto tmp = rev_res[i]; + rev_res[i] = rev_res[j]; + rev_res[j] = tmp; + } + } + } + + std::array()> sorted_values{}; + // reverse the sorted indices + std::array()> res{}; + for (std::size_t i = 0; i < enum_count(); ++i) { + res[rev_res[i]] = i; + sorted_values[i] = orig_values[rev_res[i]]; + } + + return std::pair{sorted_values, res}; + } + + static constexpr auto indices = get_indices(); + + public: + [[nodiscard]] static constexpr const E* begin() noexcept { return indices.first.data(); } + + [[nodiscard]] static constexpr const E* end() noexcept { return indices.first.data() + indices.first.size(); } + + [[nodiscard]] static constexpr const E* it(std::size_t i) noexcept { return indices.first.data() + i; } + + [[nodiscard]] static constexpr optional at(E val) noexcept { + if (auto i = enum_index(val)) { + return indices.second[*i]; + } + return {}; + } +}; + +template +class indexing> && (std::is_same_v> || std::is_same_v>)>> { + static constexpr auto& values = enum_values(); + + public: + [[nodiscard]] static constexpr const E* begin() noexcept { return values.data(); } + + [[nodiscard]] static constexpr const E* end() noexcept { return values.data() + values.size(); } + + [[nodiscard]] static constexpr const E* it(std::size_t i) noexcept { return values.data() + i; } + + [[nodiscard]] static constexpr optional at(E val) noexcept { return enum_index(val); } +}; + +template +struct indexing { + using is_transparent = std::true_type; + + template + [[nodiscard]] static constexpr optional at(E val) noexcept { + return indexing::at(val); + } +}; + +template , typename = void> +struct name_sort_impl { + [[nodiscard]] constexpr bool operator()(E e1, E e2) const { return Cmp{}(enum_name(e1), enum_name(e2)); } +}; + +template +struct name_sort_impl { + using is_transparent = std::true_type; + + template + struct FullCmp : C {}; + + template + struct FullCmp && std::is_invocable_v>> { + [[nodiscard]] constexpr bool operator()(string_view s1, string_view s2) const { return lexicographical_compare(s1, s2); } + }; + + template + using cmp_arg_t = std::conditional_t> || std::is_constructible_v, string_view, T>; + + template + [[nodiscard]] static constexpr decltype(auto) cmp_arg(T&& value) { + using D = std::decay_t; + if constexpr (std::is_enum_v) { + return enum_name(value); + } else if constexpr (std::is_constructible_v) { + return string_view{std::forward(value)}; + } else { + return std::forward(value); + } + } + + template + [[nodiscard]] constexpr std::enable_if_t< + // at least one of need to be an enum type + (std::is_enum_v> || std::is_enum_v>) && + // if both is enum, only accept if the same enum + (!std::is_enum_v> || !std::is_enum_v> || std::is_same_v, std::decay_t>) && + // is invocable with comparator + (std::is_invocable_r_v&, cmp_arg_t, cmp_arg_t>), + bool> + operator()(E1&& e1, E2&& e2) const { + constexpr FullCmp<> cmp{}; + return cmp(cmp_arg(std::forward(e1)), cmp_arg(std::forward(e2))); + } +}; + +struct raw_access_t {}; + +template +struct FilteredIterator { + Parent parent; + Iterator first; + Iterator last; + Iterator current; + Getter getter; + Predicate predicate; + + using iterator_category = std::bidirectional_iterator_tag; + using reference = std::invoke_result_t; + using value_type = std::remove_cv_t>; + using difference_type = std::ptrdiff_t; + using pointer = std::add_pointer_t>; + + constexpr FilteredIterator() noexcept = default; + constexpr FilteredIterator(const FilteredIterator&) = default; + constexpr FilteredIterator& operator=(const FilteredIterator&) = default; + constexpr FilteredIterator(FilteredIterator&&) noexcept = default; + constexpr FilteredIterator& operator=(FilteredIterator&&) noexcept = default; + + template && std::is_convertible_v>*> + constexpr explicit FilteredIterator(const FilteredIterator& other) + : parent(other.parent), first(other.first), last(other.last), current(other.current), getter(other.getter), predicate(other.predicate) {} + + constexpr FilteredIterator(Parent p, Iterator begin, Iterator end, Iterator curr, Getter get = {}, Predicate pred = {}) + : parent(p), first(std::move(begin)), last(std::move(end)), current(std::move(curr)), getter{std::move(get)}, predicate{std::move(pred)} { + if (current == first && !predicate(parent, current)) { + ++*this; + } + } + + [[nodiscard]] constexpr reference operator*() const { return getter(parent, current); } + + [[nodiscard]] constexpr pointer operator->() const { return std::addressof(**this); } + + constexpr FilteredIterator& operator++() { + do { + ++current; + } while (current != last && !predicate(parent, current)); + return *this; + } + + [[nodiscard]] constexpr FilteredIterator operator++(int) { + FilteredIterator cp = *this; + ++*this; + return cp; + } + + constexpr FilteredIterator& operator--() { + do { + --current; + } while (current != first && !predicate(parent, current)); + return *this; + } + + [[nodiscard]] constexpr FilteredIterator operator--(int) { + FilteredIterator cp = *this; + --*this; + return cp; + } + + [[nodiscard]] friend constexpr bool operator==(const FilteredIterator& lhs, const FilteredIterator& rhs) { return lhs.current == rhs.current; } + + [[nodiscard]] friend constexpr bool operator!=(const FilteredIterator& lhs, const FilteredIterator& rhs) { return lhs.current != rhs.current; } +}; + +template +constexpr int countr_zero(T x) noexcept { +#if defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L + return std::countr_zero(x); +#elif defined(_MSC_VER) && !defined(__clang__) + unsigned long index; + if constexpr (sizeof(T) <= sizeof(unsigned long)) { + return _BitScanForward(&index, static_cast(x)) ? static_cast(index) : static_cast(sizeof(T) * 8); + } else { +# ifdef _WIN64 + return _BitScanForward64(&index, static_cast(x)) ? static_cast(index) : static_cast(sizeof(T) * 8); +# else + if (_BitScanForward(&index, static_cast(x))) { return static_cast(index); } + return _BitScanForward(&index, static_cast(x >> 32)) ? static_cast(index) + 32 : static_cast(sizeof(T) * 8); +# endif + } +#else + if constexpr (sizeof(T) <= sizeof(unsigned int)) { + return x ? __builtin_ctz(static_cast(x)) : static_cast(sizeof(T) * 8); + } else if constexpr (sizeof(T) <= sizeof(unsigned long)) { + return x ? __builtin_ctzl(static_cast(x)) : static_cast(sizeof(T) * 8); + } else { + return x ? __builtin_ctzll(static_cast(x)) : static_cast(sizeof(T) * 8); + } +#endif +} + +template +constexpr int countl_zero(T x) noexcept { +#if defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L + return std::countl_zero(x); +#elif defined(_MSC_VER) && !defined(__clang__) + unsigned long index; + if constexpr (sizeof(T) <= sizeof(unsigned long)) { + return _BitScanReverse(&index, static_cast(x)) ? static_cast(sizeof(T) * 8) - static_cast(index) - 1 : static_cast(sizeof(T) * 8); + } else { +# ifdef _WIN64 + return _BitScanReverse64(&index, static_cast(x)) ? static_cast(sizeof(T) * 8) - static_cast(index) - 1 : static_cast(sizeof(T) * 8); +# else + if (_BitScanReverse(&index, static_cast(x >> 32))) { return static_cast(sizeof(T) * 8) - static_cast(index) - 33; } + return _BitScanReverse(&index, static_cast(x)) ? static_cast(sizeof(T) * 8) - static_cast(index) - 1 : static_cast(sizeof(T) * 8); +# endif + } +#else + // __builtin_clz* counts leading zeros in the promoted type width, not in T. + // We must subtract the extra bits introduced by zero-extension. + if constexpr (sizeof(T) <= sizeof(unsigned int)) { + return x ? __builtin_clz(static_cast(x)) - static_cast((sizeof(unsigned int) - sizeof(T)) * 8) : static_cast(sizeof(T) * 8); + } else if constexpr (sizeof(T) <= sizeof(unsigned long)) { + return x ? __builtin_clzl(static_cast(x)) - static_cast((sizeof(unsigned long) - sizeof(T)) * 8) : static_cast(sizeof(T) * 8); + } else { + return x ? __builtin_clzll(static_cast(x)) - static_cast((sizeof(unsigned long long) - sizeof(T)) * 8) : static_cast(sizeof(T) * 8); + } +#endif +} + +template +constexpr int bit_width(T x) noexcept { +#if defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L + return std::bit_width(x); +#else + return std::numeric_limits::digits - countl_zero(x); +#endif +} + +template +constexpr bool valid_indexing() noexcept { + constexpr std::size_t count = enum_count(); + if constexpr (count == 0) { + return false; + } else { + std::array used_indices{}; + for (const auto value : enum_values()) { + const auto index = Index::at(value); + if (!index || *index >= count || used_indices[*index]) { + return false; + } + used_indices[*index] = true; + } + return true; + } +} + +} // namespace detail + +template +using name_less = detail::name_sort_impl; + +template +using name_greater = detail::name_sort_impl>; + +using name_less_case_insensitive = detail::name_sort_impl>>; + +using name_greater_case_insensitive = detail::name_sort_impl>>; + +template +using default_indexing = detail::indexing; + +template > +using comparator_indexing = detail::indexing; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ARRAY // +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +template > +struct array { + static_assert(std::is_enum_v, "magic_enum::containers::array requires enum type."); + static_assert(detail::valid_indexing(), "magic_enum::containers::array requires non-empty reflected enum and valid indexing."); + + using index_type = Index; + using container_type = std::array()>; + + using value_type = typename container_type::value_type; + using size_type = typename container_type::size_type; + using difference_type = typename container_type::difference_type; + using reference = typename container_type::reference; + using const_reference = typename container_type::const_reference; + using pointer = typename container_type::pointer; + using const_pointer = typename container_type::const_pointer; + using iterator = typename container_type::iterator; + using const_iterator = typename container_type::const_iterator; + using reverse_iterator = typename container_type::reverse_iterator; + using const_reverse_iterator = typename container_type::const_reverse_iterator; + + constexpr reference at(E pos) { + if (auto index = index_type::at(pos); index && *index < a.size()) { + return a[*index]; + } + MAGIC_ENUM_CONTAINERS_THROW(std::out_of_range("magic_enum::containers::array::at: Unrecognized position")); + } + + constexpr const_reference at(E pos) const { + if (auto index = index_type::at(pos); index && *index < a.size()) { + return a[*index]; + } + MAGIC_ENUM_CONTAINERS_THROW(std::out_of_range("magic_enum::containers::array::at: Unrecognized position")); + } + + [[nodiscard]] constexpr reference operator[](E pos) { + auto i = index_type::at(pos); + return MAGIC_ENUM_ASSERT(i && *i < a.size()), a[*i]; + } + + [[nodiscard]] constexpr const_reference operator[](E pos) const { + auto i = index_type::at(pos); + return MAGIC_ENUM_ASSERT(i && *i < a.size()), a[*i]; + } + + [[nodiscard]] constexpr reference front() noexcept { return a.front(); } + + [[nodiscard]] constexpr const_reference front() const noexcept { return a.front(); } + + [[nodiscard]] constexpr reference back() noexcept { return a.back(); } + + [[nodiscard]] constexpr const_reference back() const noexcept { return a.back(); } + + [[nodiscard]] constexpr pointer data() noexcept { return a.data(); } + + [[nodiscard]] constexpr const_pointer data() const noexcept { return a.data(); } + + [[nodiscard]] constexpr iterator begin() noexcept { return a.begin(); } + + [[nodiscard]] constexpr const_iterator begin() const noexcept { return a.begin(); } + + [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return a.cbegin(); } + + [[nodiscard]] constexpr iterator end() noexcept { return a.end(); } + + [[nodiscard]] constexpr const_iterator end() const noexcept { return a.end(); } + + [[nodiscard]] constexpr const_iterator cend() const noexcept { return a.cend(); } + + [[nodiscard]] constexpr reverse_iterator rbegin() noexcept { return a.rbegin(); } + + [[nodiscard]] constexpr const_reverse_iterator rbegin() const noexcept { return a.rbegin(); } + + [[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept { return a.crbegin(); } + + [[nodiscard]] constexpr reverse_iterator rend() noexcept { return a.rend(); } + + [[nodiscard]] constexpr const_reverse_iterator rend() const noexcept { return a.rend(); } + + [[nodiscard]] constexpr const_reverse_iterator crend() const noexcept { return a.crend(); } + + [[nodiscard]] constexpr bool empty() const noexcept { return a.empty(); } + + [[nodiscard]] constexpr size_type size() const noexcept { return a.size(); } + + [[nodiscard]] constexpr size_type max_size() const noexcept { return a.max_size(); } + + constexpr void fill(const V& value) { + for (auto& v : a) { + v = value; + } + } + + constexpr void swap(array& other) noexcept(std::is_nothrow_move_constructible_v && std::is_nothrow_move_assignable_v) { + for (std::size_t i = 0; i < a.size(); ++i) { + auto v = std::move(other.a[i]); + other.a[i] = std::move(a[i]); + a[i] = std::move(v); + } + } + + [[nodiscard]] friend constexpr bool operator==(const array& a1, const array& a2) { return detail::equal(a1, a2); } + + [[nodiscard]] friend constexpr bool operator!=(const array& a1, const array& a2) { return !detail::equal(a1, a2); } + + [[nodiscard]] friend constexpr bool operator<(const array& a1, const array& a2) { return detail::lexicographical_compare(a1, a2); } + + [[nodiscard]] friend constexpr bool operator<=(const array& a1, const array& a2) { return !detail::lexicographical_compare(a2, a1); } + + [[nodiscard]] friend constexpr bool operator>(const array& a1, const array& a2) { return detail::lexicographical_compare(a2, a1); } + + [[nodiscard]] friend constexpr bool operator>=(const array& a1, const array& a2) { return !detail::lexicographical_compare(a1, a2); } + + container_type a; +}; + +namespace detail { + +template +constexpr array> to_array_impl(T(&a)[N], std::index_sequence) { + return {{a[J]...}}; +} + +template +constexpr array> to_array_impl(T(&&a)[N], std::index_sequence) { + return {{std::move(a[J])...}}; +} + +} // namespace detail + +template +constexpr std::enable_if_t<(enum_count() == N), array>> to_array(T(&a)[N]) { + return detail::to_array_impl(a, std::make_index_sequence{}); +} + +template +constexpr std::enable_if_t<(enum_count() == N), array>> to_array(T(&&a)[N]) { + return detail::to_array_impl(std::move(a), std::make_index_sequence{}); +} + +template +constexpr std::enable_if_t<(enum_count() == sizeof...(Ts)), array>>> make_array(Ts&&... ts) { + return {{std::forward(ts)...}}; +} + +inline constexpr detail::raw_access_t raw_access{}; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// BITSET // +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +template > +class bitset { + static_assert(std::is_enum_v, "magic_enum::containers::bitset requires enum type."); + static_assert(detail::valid_indexing(), "magic_enum::containers::bitset requires non-empty reflected enum and valid indexing."); + + using base_type = std::conditional_t() <= 8, std::uint_least8_t, + std::conditional_t() <= 16, std::uint_least16_t, + std::conditional_t() <= 32, std::uint_least32_t, + std::uint_least64_t>>>; + + static constexpr std::size_t bits_per_base = sizeof(base_type) * 8; + static constexpr std::size_t base_type_count = (enum_count() > 0 ? (enum_count() - 1) / bits_per_base + 1 : 0); + static constexpr std::size_t not_interested = base_type_count * bits_per_base - enum_count(); + static constexpr base_type last_value_max = [] { + if constexpr (not_interested == 0) { + return (std::numeric_limits::max)(); + } else { + return static_cast((base_type{1} << (bits_per_base - not_interested)) - 1); + } + }(); + + [[nodiscard]] static constexpr base_type bit_mask(std::size_t index) noexcept { + return static_cast(base_type{1} << (index % bits_per_base)); + } + + [[nodiscard]] static constexpr base_type least_significant_bit(base_type value) noexcept { + MAGIC_ENUM_ASSERT(value != 0); + return bit_mask(static_cast(detail::countr_zero(value))); + } + + template + class reference_impl { + friend class bitset; + + parent_t parent; + std::size_t num_index; + base_type bit_index; + + constexpr reference_impl(parent_t p, std::size_t i) noexcept : reference_impl(p, i / bits_per_base, bit_mask(i)) {} + + constexpr reference_impl(parent_t p, std::size_t num, base_type bit) noexcept : parent(p), num_index(num), bit_index(bit) {} + + public: + constexpr reference_impl& operator=(bool v) noexcept { + if (v) { + parent->a[num_index] |= bit_index; + } else { + parent->a[num_index] &= ~bit_index; + } + return *this; + } + + constexpr reference_impl& operator=(const reference_impl& v) noexcept { + if (this == &v) { + return *this; + } + *this = static_cast(v); + return *this; + } + + [[nodiscard]] constexpr operator bool() const noexcept { return (parent->a[num_index] & bit_index) > 0; } + + [[nodiscard]] constexpr bool operator~() const noexcept { return !static_cast(*this); } + + constexpr reference_impl& flip() noexcept { + *this = ~*this; + return *this; + } + }; + + template + [[nodiscard]] constexpr T to_(detail::raw_access_t) const { + if constexpr (std::numeric_limits::digits < std::numeric_limits::digits) { + if (a[0] > static_cast((std::numeric_limits::max)())) { + MAGIC_ENUM_CONTAINERS_THROW(std::overflow_error("magic_enum::containers::bitset::to: Cannot represent enum in this type")); + } + } + for (std::size_t i = 1; i < base_type_count; ++i) { + if (a[i] != 0) { + MAGIC_ENUM_CONTAINERS_THROW(std::overflow_error("magic_enum::containers::bitset::to: Cannot represent enum in this type")); + } + } + return static_cast(a[0]); + } + + template + class iterator_impl { + friend class bitset; + + parent_t parent = nullptr; + std::size_t num_index = 0; + base_type bit_index = 0; + public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = E; + using difference_type = std::ptrdiff_t; + using pointer = const E*; + using reference = const E&; + + constexpr iterator_impl() noexcept = default; + constexpr iterator_impl(const iterator_impl&) noexcept = default; + constexpr iterator_impl& operator=(const iterator_impl&) noexcept = default; + constexpr iterator_impl(iterator_impl&&) noexcept = default; + constexpr iterator_impl& operator=(iterator_impl&&) noexcept = default; + + template >> + constexpr iterator_impl(const iterator_impl& other) noexcept + : parent(other.parent), num_index(other.num_index), bit_index(other.bit_index) {} + + private: + template + friend class iterator_impl; + constexpr iterator_impl(parent_t p, std::size_t i) noexcept : iterator_impl(p, i / bits_per_base, bit_mask(i)) {} + + constexpr iterator_impl(parent_t p, std::size_t num, base_type bit) noexcept : parent(p), num_index(num), bit_index(bit) {} + + [[nodiscard]] static constexpr iterator_impl begin(parent_t p) noexcept { + for (std::size_t num_index = 0; num_index < base_type_count; ++num_index) { + if (p->a[num_index] > 0) { + const auto bit_index = least_significant_bit(p->a[num_index]); + return iterator_impl(p, num_index, bit_index); + } + } + return end(p); + } + [[nodiscard]] static constexpr iterator_impl end(parent_t p) noexcept { + return iterator_impl(p, enum_count()); + } + + public: + [[nodiscard]] constexpr reference operator*() const noexcept { return *Index::it(num_index * bits_per_base + static_cast(detail::countr_zero(bit_index))); } + + [[nodiscard]] constexpr pointer operator->() const noexcept { return std::addressof(**this); } + + constexpr iterator_impl& operator++() noexcept { + const auto lower_bits = static_cast((bit_index << 1) - 1); + auto remaining_bits = static_cast(parent->a[num_index] & static_cast(~lower_bits)); + while (remaining_bits == 0 && ++num_index < base_type_count) { + remaining_bits = parent->a[num_index]; + } + if (num_index >= base_type_count) { + return *this = end(parent); + } + bit_index = least_significant_bit(remaining_bits); + return *this; + } + + [[nodiscard]] constexpr iterator_impl operator++(int) noexcept { + iterator_impl cp = *this; + ++*this; + return cp; + } + + constexpr iterator_impl& operator--() noexcept { + base_type search_mask; + if (num_index >= base_type_count) { + num_index = base_type_count - 1; + search_mask = last_value_max; + } else if (num_index == base_type_count - 1 && bit_index > last_value_max) { + search_mask = last_value_max; + } else { + search_mask = static_cast(bit_index - 1); + } + + auto remaining_bits = static_cast(parent->a[num_index] & search_mask); + while (remaining_bits == 0 && num_index != 0) { + remaining_bits = parent->a[--num_index]; + } + if (remaining_bits == 0) { + num_index = (std::numeric_limits::max)(); + bit_index = static_cast(base_type{1} << (bits_per_base - 1)); + return *this; + } + bit_index = static_cast(base_type{1} << (detail::bit_width(remaining_bits) - 1)); + return *this; + } + + [[nodiscard]] constexpr iterator_impl operator--(int) noexcept { + iterator_impl cp = *this; + --*this; + return cp; + } + + [[nodiscard]] friend constexpr bool operator==(const iterator_impl& lhs, const iterator_impl& rhs) { return lhs.parent == rhs.parent && lhs.num_index == rhs.num_index && lhs.bit_index == rhs.bit_index; } + + [[nodiscard]] friend constexpr bool operator!=(const iterator_impl& lhs, const iterator_impl& rhs) { return !(lhs == rhs); } + }; + + public: + using index_type = Index; + using container_type = std::array; + using reference = reference_impl<>; + using const_reference = reference_impl; + using iterator = iterator_impl<>; + using const_iterator = iterator_impl; + + constexpr explicit bitset(detail::raw_access_t = raw_access) noexcept : a{{}} {} + + constexpr explicit bitset(detail::raw_access_t, unsigned long long val) : a{{}} { + if constexpr (enum_count() < std::numeric_limits::digits) { + if ((val >> enum_count()) != 0) { + MAGIC_ENUM_CONTAINERS_THROW(std::out_of_range("magic_enum::containers::bitset::constructor: Upper bit set in raw number")); + } + } + a[0] = static_cast(val); + } + + constexpr explicit bitset(detail::raw_access_t, string_view sv, string_view::size_type pos = 0, string_view::size_type n = string_view::npos, char_type zero = char_type{'0'}, char_type one = char_type{'1'}) + : a{{}} { + std::size_t i = 0; + for (auto c : sv.substr(pos, n)) { + if (c == one) { + if (i >= enum_count()) { + MAGIC_ENUM_CONTAINERS_THROW(std::out_of_range("magic_enum::containers::bitset::constructor: Upper bit set in raw string")); + } + reference{this, i} = true; + } else if (c != zero) { + MAGIC_ENUM_CONTAINERS_THROW(std::invalid_argument("magic_enum::containers::bitset::constructor: Unrecognized character in raw string")); + } + ++i; + } + } + + constexpr explicit bitset(detail::raw_access_t, const char_type* str, std::size_t n = ~std::size_t{0}, char_type zero = char_type{'0'}, char_type one = char_type{'1'}) + : bitset(detail::raw_access_t{}, n == string_view::npos ? string_view{str} : string_view{str, n}, 0, n, zero, one) {} + + constexpr bitset(std::initializer_list starters) : a{{}} { + if constexpr (magic_enum::detail::subtype_v == magic_enum::detail::enum_subtype::flags) { + for (auto& f : starters) { + *this |= bitset(f); + } + } else { + for (auto& f : starters) { + set(f); + } + } + } + template && magic_enum::detail::subtype_v == magic_enum::detail::enum_subtype::flags, int> = 0> + constexpr explicit bitset(V starter) : a{{}} { + auto u = enum_underlying(starter); + for (E v : enum_values()) { + if (u == 0) { + break; + } + if (auto ul = enum_underlying(v); (ul & u) != 0) { + u &= ~ul; + (*this)[v] = true; + } + } + if (u != 0) { + MAGIC_ENUM_CONTAINERS_THROW(std::invalid_argument("magic_enum::containers::bitset::constructor: Unrecognized enum value in flag")); + } + } + + template > + constexpr explicit bitset(string_view sv, Cmp&& cmp = {}, char_type sep = char_type{'|'}) : a{{}} { + for (std::size_t to = 0; (to = magic_enum::detail::find(sv, sep)) != string_view::npos; sv.remove_prefix(to + 1)) { + if (auto v = enum_cast, Cmp&>(sv.substr(0, to), cmp)) { + set(*v); + } else { + MAGIC_ENUM_CONTAINERS_THROW(std::invalid_argument("magic_enum::containers::bitset::constructor: Unrecognized enum value in string")); + } + } + if (!sv.empty()) { + if (auto v = enum_cast, Cmp&>(sv, cmp)) { + set(*v); + } else { + MAGIC_ENUM_CONTAINERS_THROW(std::invalid_argument("magic_enum::containers::bitset::constructor: Unrecognized enum value in string")); + } + } + } + + [[nodiscard]] friend constexpr bool operator==(const bitset& lhs, const bitset& rhs) noexcept { return detail::equal(lhs.a, rhs.a); } + + [[nodiscard]] friend constexpr bool operator!=(const bitset& lhs, const bitset& rhs) noexcept { return !detail::equal(lhs.a, rhs.a); } + + [[nodiscard]] constexpr bool operator[](E pos) const { + auto i = index_type::at(pos); + return MAGIC_ENUM_ASSERT(i && *i < size()), static_cast(const_reference(this, *i)); + } + + [[nodiscard]] constexpr reference operator[](E pos) { + auto i = index_type::at(pos); + return MAGIC_ENUM_ASSERT(i && *i < size()), reference{this, *i}; + } + + [[nodiscard]] constexpr iterator begin() noexcept { return iterator::begin(this); } + + [[nodiscard]] constexpr const_iterator begin() const noexcept { return const_iterator::begin(this); } + + [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return const_iterator::begin(this); } + + [[nodiscard]] constexpr iterator end() noexcept { return iterator::end(this); } + + [[nodiscard]] constexpr const_iterator end() const noexcept { return const_iterator::end(this); } + + [[nodiscard]] constexpr const_iterator cend() const noexcept { return const_iterator::end(this); } + + [[nodiscard]] constexpr const_iterator find(E pos) const noexcept { + if (auto i = index_type::at(pos); i && *i < size() && static_cast(const_reference(this, *i))) { + return const_iterator(this, *i); + } + return end(); + } + + [[nodiscard]] constexpr iterator find(E pos) noexcept { + if (auto i = index_type::at(pos); i && *i < size() && static_cast(const_reference(this, *i))) { + return iterator(this, *i); + } + return end(); + } + + constexpr bool test(E pos) const { + if (auto i = index_type::at(pos); i && *i < size()) { + return static_cast(const_reference(this, *i)); + } + MAGIC_ENUM_CONTAINERS_THROW(std::out_of_range("magic_enum::containers::bitset::test: Unrecognized position")); + } + + [[nodiscard]] constexpr bool all() const noexcept { + for (std::size_t i = 0; i + 1 < base_type_count; ++i) { + if (a[i] != (std::numeric_limits::max)()) { + return false; + } + } + return a[base_type_count - 1] == last_value_max; + } + + [[nodiscard]] constexpr bool any() const noexcept { + for (auto& v : a) { + if (v > 0) { + return true; + } + } + return false; + } + + [[nodiscard]] constexpr bool none() const noexcept { return !any(); } + + [[nodiscard]] constexpr std::size_t count() const noexcept { + std::size_t c = 0; + for (auto& v : a) { + c += detail::popcount(v); + } + return c; + } + + [[nodiscard]] constexpr std::size_t size() const noexcept { return enum_count(); } + + [[nodiscard]] constexpr std::size_t max_size() const noexcept { return enum_count(); } + + constexpr bitset& operator&=(const bitset& other) noexcept { + for (std::size_t i = 0; i < base_type_count; ++i) { + a[i] &= other.a[i]; + } + return *this; + } + + constexpr bitset& operator|=(const bitset& other) noexcept { + for (std::size_t i = 0; i < base_type_count; ++i) { + a[i] |= other.a[i]; + } + return *this; + } + + constexpr bitset& operator^=(const bitset& other) noexcept { + for (std::size_t i = 0; i < base_type_count; ++i) { + a[i] ^= other.a[i]; + } + return *this; + } + + [[nodiscard]] constexpr bitset operator~() const noexcept { + bitset res = *this; + res.flip(); + return res; + } + + constexpr bitset& set() noexcept { + for (std::size_t i = 0; i + 1 < base_type_count; ++i) { + a[i] = (std::numeric_limits::max)(); + } + a[base_type_count - 1] = last_value_max; + return *this; + } + + constexpr bitset& set(E pos, bool value = true) { + if (auto i = index_type::at(pos); i && *i < size()) { + reference{this, *i} = value; + return *this; + } + MAGIC_ENUM_CONTAINERS_THROW(std::out_of_range("magic_enum::containers::bitset::set: Unrecognized position")); + } + + constexpr bitset& reset() noexcept { return *this = bitset{}; } + + constexpr bitset& reset(E pos) { + if (auto i = index_type::at(pos); i && *i < size()) { + reference{this, *i} = false; + return *this; + } + MAGIC_ENUM_CONTAINERS_THROW(std::out_of_range("magic_enum::containers::bitset::reset: Unrecognized position")); + } + + constexpr bitset& flip() noexcept { + for (auto& value : a) { + value = static_cast(~value); + } + a[base_type_count - 1] &= last_value_max; + return *this; + } + + [[nodiscard]] friend constexpr bitset operator&(const bitset& lhs, const bitset& rhs) noexcept { + bitset cp = lhs; + cp &= rhs; + return cp; + } + + [[nodiscard]] friend constexpr bitset operator|(const bitset& lhs, const bitset& rhs) noexcept { + bitset cp = lhs; + cp |= rhs; + return cp; + } + + [[nodiscard]] friend constexpr bitset operator^(const bitset& lhs, const bitset& rhs) noexcept { + bitset cp = lhs; + cp ^= rhs; + return cp; + } + + template + [[nodiscard]] constexpr explicit operator std::enable_if_t == magic_enum::detail::enum_subtype::flags, E>() const { + underlying_type_t res = 0; + for (const auto value : *this) { + res |= enum_underlying(value); + } + return static_cast(res); + } + + [[nodiscard]] string to_string(char_type sep = char_type{'|'}) const { + string name; + + for (const auto& e : enum_values()) { + if (test(e)) { + if (!name.empty()) { + name.append(1, sep); + } + auto n = enum_name(e); + name.append(n.data(), n.size()); + } + } + return name; + } + + [[nodiscard]] string to_string(detail::raw_access_t, char_type zero = char_type{'0'}, char_type one = char_type{'1'}) const { + string name; + name.reserve(size()); + for (std::size_t i = 0; i < size(); ++i) { + name.append(1, const_reference{this, i} ? one : zero); + } + return name; + } + + [[nodiscard]] constexpr unsigned long long to_ullong(detail::raw_access_t raw) const { return to_(raw); } + + [[nodiscard]] constexpr unsigned long to_ulong(detail::raw_access_t raw) const { return to_(raw); } + + template + friend std::basic_ostream& operator<<(std::basic_ostream& o, const bitset& bs) { + const auto s = bs.to_string(); + return o.write(s.data(), static_cast(s.size())); + } + + template + friend std::basic_istream& operator>>(std::basic_istream& i, bitset& bs) { + std::basic_string s; + if (i >> s; !s.empty()) { + bs = bitset(string_view{s.data(), s.size()}); + } + return i; + } + + private: + container_type a; +}; + +template +explicit bitset(V starter) -> bitset; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SET // +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +template > +class set { + using index_type = detail::indexing; + struct Getter { + constexpr const E& operator()(const set*, const E* p) const noexcept { return *p; } + }; + struct Predicate { + constexpr bool operator()(const set* h, const E* e) const noexcept { return h->a[*e]; } + }; + + public: + using container_type = bitset; + using key_type = E; + using value_type = E; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using key_compare = Cmp; + using value_compare = Cmp; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using iterator = detail::FilteredIterator; + using const_iterator = detail::FilteredIterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + constexpr set() noexcept = default; + + template + constexpr set(InputIt first, InputIt last) { + while (first != last) { + insert(*first++); + } + } + + constexpr set(std::initializer_list ilist) { + for (auto e : ilist) { + insert(e); + } + } + template && magic_enum::detail::subtype_v == magic_enum::detail::enum_subtype::flags, int> = 0> + constexpr explicit set(V starter) { + auto u = enum_underlying(starter); + for (E v : enum_values()) { + if ((enum_underlying(v) & u) != 0) { + insert(v); + } + } + } + + constexpr set(const set&) noexcept = default; + constexpr set(set&&) noexcept = default; + + constexpr set& operator=(const set&) noexcept = default; + constexpr set& operator=(set&&) noexcept = default; + constexpr set& operator=(std::initializer_list ilist) { + clear(); + for (auto e : ilist) { + insert(e); + } + return *this; + } + + constexpr const_iterator begin() const noexcept { + return const_iterator{this, index_type::begin(), index_type::end(), index_type::begin()}; + } + + constexpr const_iterator end() const noexcept { + return const_iterator{this, index_type::begin(), index_type::end(), index_type::end()}; + } + + constexpr const_iterator cbegin() const noexcept { return begin(); } + + constexpr const_iterator cend() const noexcept { return end(); } + + constexpr const_reverse_iterator rbegin() const noexcept { return {end()}; } + + constexpr const_reverse_iterator rend() const noexcept { return {begin()}; } + + constexpr const_reverse_iterator crbegin() const noexcept { return rbegin(); } + + constexpr const_reverse_iterator crend() const noexcept { return rend(); } + + [[nodiscard]] constexpr bool empty() const noexcept { return s == 0; } + + [[nodiscard]] constexpr size_type size() const noexcept { return s; } + + [[nodiscard]] constexpr size_type max_size() const noexcept { return a.max_size(); } + + constexpr void clear() noexcept { + a.reset(); + s = 0; + } + + constexpr std::pair insert(const value_type& value) noexcept { + if (auto i = index_type::at(value)) { + auto ref = a[value]; + const bool res = !ref; + if (res) { + ref = true; + ++s; + } + + return {iterator{this, index_type::begin(), index_type::end(), index_type::it(*i)}, res}; + } + return {end(), false}; + } + + constexpr std::pair insert(value_type&& value) noexcept { return insert(value); } + + constexpr iterator insert(const_iterator, const value_type& value) noexcept { return insert(value).first; } + + constexpr iterator insert(const_iterator hint, value_type&& value) noexcept { return insert(hint, value); } + + template + constexpr void insert(InputIt first, InputIt last) { + while (first != last) { + insert(*first++); + } + } + + constexpr void insert(std::initializer_list ilist) noexcept { + for (auto v : ilist) { + insert(v); + } + } + + template + constexpr std::pair emplace(Args&&... args) { + return insert(value_type{std::forward(args)...}); + } + + template + constexpr iterator emplace_hint(const_iterator, Args&&... args) { + return emplace(std::forward(args)...).first; + } + + constexpr iterator erase(const_iterator pos) noexcept { + erase(*pos++); + return pos; + } + + constexpr iterator erase(const_iterator first, const_iterator last) noexcept { + while (first != last) { + first = erase(first); + } + return first; + } + + constexpr size_type erase(const key_type& key) noexcept { + if (index_type::at(key)) { + auto ref = a[key]; + const bool res = ref; + if (res) { + --s; + } + ref = false; + return res; + } + return 0; + } + + template + constexpr std::enable_if_t && !std::is_same_v, key_type>, size_type> erase(K&& x) { + size_type c = 0; + for (auto [first, last] = detail::equal_range(index_type::begin(), index_type::end(), x, key_compare{}); first != last;) { + c += erase(*first++); + } + return c; + } + + void swap(set& other) noexcept { + std::swap(a, other.a); + std::swap(s, other.s); + } + + [[nodiscard]] constexpr size_type count(const key_type& key) const noexcept { return a.find(key) != a.end(); } + + template + [[nodiscard]] constexpr std::enable_if_t, size_type> count(const K& x) const { + size_type c = 0; + for (auto [first, last] = detail::equal_range(index_type::begin(), index_type::end(), x, key_compare{}); first != last; ++first) { + c += a.test(*first); + } + return c; + } + + [[nodiscard]] constexpr const_iterator find(const key_type& key) const noexcept { + if (auto i = index_type::at(key); i && a.test(key)) { + return const_iterator{this, index_type::begin(), index_type::end(), index_type::it(*i)}; + } + return end(); + } + + template + [[nodiscard]] constexpr std::enable_if_t, const_iterator> find(const K& x) const { + for (auto [first, last] = detail::equal_range(index_type::begin(), index_type::end(), x, key_compare{}); first != last; ++first) { + if (a.test(*first)) { + return const_iterator{this, index_type::begin(), index_type::end(), first}; + } + } + return end(); + } + + [[nodiscard]] constexpr bool contains(const key_type& key) const noexcept { return count(key) > 0; } + + template + [[nodiscard]] constexpr std::enable_if_t, bool> contains(const K& x) const { + return find(x) != end(); + } + + private: + [[nodiscard]] constexpr const_iterator iterator_at_or_after(const E* it) const noexcept { + while (it != index_type::end() && !a.test(*it)) { + ++it; + } + return const_iterator{this, index_type::begin(), index_type::end(), it}; + } + + public: + [[nodiscard]] constexpr std::pair equal_range(const key_type& key) const { + return {lower_bound(key), upper_bound(key)}; + } + + template + [[nodiscard]] constexpr std::enable_if_t, std::pair> equal_range(const K& x) const { + return {lower_bound(x), upper_bound(x)}; + } + + [[nodiscard]] constexpr const_iterator lower_bound(const key_type& key) const { + return iterator_at_or_after(detail::impl::lower_bound(index_type::begin(), index_type::end(), key, key_compare{})); + } + + template + [[nodiscard]] constexpr std::enable_if_t, const_iterator> lower_bound(const K& x) const { + return iterator_at_or_after(detail::impl::lower_bound(index_type::begin(), index_type::end(), x, key_compare{})); + } + + [[nodiscard]] constexpr const_iterator upper_bound(const key_type& key) const { + return iterator_at_or_after(detail::upper_bound(index_type::begin(), index_type::end(), key, key_compare{})); + } + + template + [[nodiscard]] constexpr std::enable_if_t, const_iterator> upper_bound(const K& x) const { + return iterator_at_or_after(detail::upper_bound(index_type::begin(), index_type::end(), x, key_compare{})); + } + + [[nodiscard]] constexpr key_compare key_comp() const { return {}; } + + [[nodiscard]] constexpr value_compare value_comp() const { return {}; } + + [[nodiscard]] constexpr friend bool operator==(const set& lhs, const set& rhs) noexcept { return lhs.a == rhs.a; } + + [[nodiscard]] constexpr friend bool operator!=(const set& lhs, const set& rhs) noexcept { return lhs.a != rhs.a; } + + [[nodiscard]] constexpr friend bool operator<(const set& lhs, const set& rhs) { + return detail::lexicographical_compare(lhs, rhs); + } + + [[nodiscard]] constexpr friend bool operator<=(const set& lhs, const set& rhs) { return !(rhs < lhs); } + + [[nodiscard]] constexpr friend bool operator>(const set& lhs, const set& rhs) { return rhs < lhs; } + + [[nodiscard]] constexpr friend bool operator>=(const set& lhs, const set& rhs) { return !(lhs < rhs); } + + template + size_type erase_if(Pred pred) { + auto old_size = size(); + for (auto i = begin(), last = end(); i != last;) { + if (pred(*i)) { + i = erase(i); + } else { + ++i; + } + } + return old_size - size(); + } + + private: + container_type a; + std::size_t s = 0; +}; + +template +explicit set(V starter) -> set; + +template +constexpr std::enable_if_t<(std::is_integral_v && J < enum_count()), V&> get(array& a) noexcept { + return a.a[J]; +} + +template +constexpr std::enable_if_t<(std::is_integral_v && J < enum_count()), V&&> get(array&& a) noexcept { + return std::move(a.a[J]); +} + +template +constexpr std::enable_if_t<(std::is_integral_v && J < enum_count()), const V&> get(const array& a) noexcept { + return a.a[J]; +} + +template +constexpr std::enable_if_t<(std::is_integral_v && J < enum_count()), const V&&> get(const array&& a) noexcept { + return std::move(a.a[J]); +} + +template +constexpr std::enable_if_t && enum_contains(Enum), V&> get(array& a) { + return a[Enum]; +} + +template +constexpr std::enable_if_t && enum_contains(Enum), V&&> get(array&& a) { + return std::move(a[Enum]); +} + +template +constexpr std::enable_if_t && enum_contains(Enum), const V&> get(const array& a) { + return a[Enum]; +} + +template +constexpr std::enable_if_t && enum_contains(Enum), const V&&> get(const array&& a) { + return std::move(a[Enum]); +} + +} // namespace magic_enum::containers + +template +struct std::hash> { + std::size_t operator()(const magic_enum::containers::bitset& bs) const noexcept { + if constexpr (magic_enum::enum_count() <= sizeof(unsigned long long) * 8) { + return std::hash{}(bs.to_ullong(magic_enum::containers::raw_access)); + } else { + unsigned long long low_bits = 0; + std::size_t seed = 0; + bool has_high_bits = false; + for (const auto value : bs) { + const auto index = Index::at(value); + MAGIC_ENUM_ASSERT(index); + if (*index < sizeof(low_bits) * 8) { + low_bits |= 1ULL << *index; + } else { + if (!has_high_bits) { + seed = std::hash{}(low_bits); + has_high_bits = true; + } + const auto index_hash = std::hash{}(*index); + seed ^= index_hash + std::size_t{0x9e3779b9U} + (seed << 6) + (seed >> 2); + } + } + return has_high_bits ? seed : std::hash{}(low_bits); + } + } +}; + +#undef MAGIC_ENUM_CONTAINERS_THROW + +#endif // NEARGYE_MAGIC_ENUM_CONTAINERS_HPP diff --git a/build-config/magicenum/include/magic_enum/magic_enum_flags.hpp b/build-config/magicenum/include/magic_enum/magic_enum_flags.hpp new file mode 100644 index 0000000..da51176 --- /dev/null +++ b/build-config/magicenum/include/magic_enum/magic_enum_flags.hpp @@ -0,0 +1,197 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.8 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2026 Daniil Goncharov . +// +// 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 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. + +#ifndef NEARGYE_MAGIC_ENUM_FLAGS_HPP +#define NEARGYE_MAGIC_ENUM_FLAGS_HPP + +#include "magic_enum.hpp" + +namespace magic_enum { + +namespace detail { + +template +constexpr auto values_ors() noexcept { + using U = make_unsigned_t>; + auto ors = U{0}; + for (const auto value : values_v) { + ors |= static_cast(value); + } + + return ors; +} + +} // namespace magic_enum::detail + +// Returns name from flag enum value. +// If flag enum value has no name or is out of range, returns empty string. +template +[[nodiscard]] auto enum_flags_name(E value, char_type sep = char_type{'|'}) -> detail::enable_if_t { + using D = std::decay_t; + using U = detail::make_unsigned_t>; + constexpr auto S = detail::enum_subtype::flags; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + const auto flag_value = static_cast(value); + string name; + auto check_value = U{0}; + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (const auto v = static_cast(detail::values_v[i]); (flag_value & v) != U{0}) { + if (const auto n = detail::names_v[i]; !n.empty()) { + check_value |= v; + if (!name.empty()) { + name.append(1, sep); + } + name.append(n.data(), n.size()); + } else { + return {}; // Value out of range. + } + } + } + + if (check_value != U{0} && check_value == flag_value) { + return name; + } + return {}; // Invalid value or out of range. +} + +// Returns flag enum value from integer value. +// Returns optional containing flag enum value. +template +[[nodiscard]] constexpr auto enum_flags_cast(underlying_type_t value) noexcept -> detail::enable_if_t>> { + using D = std::decay_t; + using U = underlying_type_t; + using V = detail::make_unsigned_t; + constexpr auto S = detail::enum_subtype::flags; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::count_v == 0) { + static_cast(value); + return {}; // Empty enum. + } else { + const auto flag_value = static_cast(value); + constexpr auto mask = detail::values_ors(); + if (flag_value != V{0} && (flag_value & static_cast(~mask)) == V{0}) { + return static_cast(value); + } + return {}; // Invalid value or out of range. + } +} + +// Returns flag enum value from name. +// Returns optional containing flag enum value. +template > +[[nodiscard]] constexpr auto enum_flags_cast(string_view value, [[maybe_unused]] char_type sep = char_type{'|'}, [[maybe_unused]] BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable_v) -> detail::enable_if_t>, BinaryPredicate> { + using D = std::decay_t; + using U = detail::make_unsigned_t>; + constexpr auto S = detail::enum_subtype::flags; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::count_v == 0) { + static_cast(value); + return {}; // Empty enum. + } else { + auto result = U{0}; + // Avoid GCC C++26 wrong-code with find/remove_prefix; see https://github.com/Neargye/magic_enum/issues/467. + for (std::size_t first = 0; first < value.size();) { + auto last = first; + while (last < value.size() && value[last] != sep) { + ++last; + } + const auto s = value.substr(first, last - first); + auto flag = U{0}; + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (detail::cmp_equal(s, detail::names_v[i], p)) { + flag = static_cast(detail::values_v[i]); + result |= flag; + break; + } + } + if (flag == U{0}) { + return {}; // Invalid value or out of range. + } + first = (last < value.size()) ? last + 1 : last; + } + + if (result != U{0}) { + return static_cast(result); + } + return {}; // Invalid value or out of range. + } +} + +// Returns true if flag enum contains specified value. +template +[[nodiscard]] constexpr auto enum_flags_contains(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + using U = underlying_type_t; + + return static_cast(enum_flags_cast(static_cast(value))); +} + +// Returns true if flag enum contains specified integer value. +template +[[nodiscard]] constexpr auto enum_flags_contains(underlying_type_t value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + + return static_cast(enum_flags_cast(value)); +} + +// Returns true if flag enum contains enumerator with specified name. +template > +[[nodiscard]] constexpr auto enum_flags_contains(string_view value, char_type sep = char_type{'|'}, BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable_v) -> detail::enable_if_t { + using D = std::decay_t; + + return static_cast(enum_flags_cast(value, sep, p)); +} + +// Returns true if `flags` contains `flag`. +// Returns false if `flag` equals 0 because 0 is not a flag. +template +constexpr auto enum_flags_test(E flags, E flag) noexcept -> detail::enable_if_t { + using U = detail::make_unsigned_t>; + + const auto flag_value = static_cast(flag); + return flag_value != U{0} && (static_cast(flags) & flag_value) == flag_value; +} + +// Returns true if `lhs` and `rhs` share any flags. +// Returns false if either value equals 0 because 0 is not a flag. +template +constexpr auto enum_flags_test_any(E lhs, E rhs) noexcept -> detail::enable_if_t { + using U = detail::make_unsigned_t>; + + return (static_cast(lhs) & static_cast(rhs)) != U{0}; +} + +} // namespace magic_enum + +#endif // NEARGYE_MAGIC_ENUM_FLAGS_HPP diff --git a/build-config/magicenum/include/magic_enum/magic_enum_format.hpp b/build-config/magicenum/include/magic_enum/magic_enum_format.hpp new file mode 100644 index 0000000..294fd65 --- /dev/null +++ b/build-config/magicenum/include/magic_enum/magic_enum_format.hpp @@ -0,0 +1,90 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.8 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2026 Daniil Goncharov . +// +// 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 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. + +#ifndef NEARGYE_MAGIC_ENUM_FORMAT_HPP +#define NEARGYE_MAGIC_ENUM_FORMAT_HPP + +#include "magic_enum.hpp" +#include "magic_enum_flags.hpp" + +namespace magic_enum::detail { + +template >, int> = 0> +std::string format_as(E e) { + using D = std::decay_t; + static_assert(std::is_same_v, "magic_enum::formatter requires string_view::value_type type same as char."); + if constexpr (magic_enum::detail::supported::value) { + if constexpr (magic_enum::detail::subtype_v == magic_enum::detail::enum_subtype::flags) { + if (const auto name = magic_enum::enum_flags_name(e); !name.empty()) { + return {name.data(), name.size()}; + } + } else { + if (const auto name = magic_enum::enum_name(e); !name.empty()) { + return {name.data(), name.size()}; + } + } + } + return std::to_string(magic_enum::enum_integer(e)); +} + +} // namespace magic_enum::detail + +#ifndef MAGIC_ENUM_USE_STD_MODULE +# if __has_include() && ((defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) || __cplusplus >= 202002L) +# include +# endif +#endif + +#if defined(__cpp_lib_format) && __cpp_lib_format >= 201907L + +template +struct std::formatter>, char>> : std::formatter { + template + auto format(E e, FormatContext& ctx) const { + return std::formatter::format(magic_enum::detail::format_as(e), ctx); + } +}; + +#endif + +#if defined(FMT_VERSION) + +template +struct fmt::formatter>, char>> : fmt::formatter { + template + auto format(E e, FormatContext& ctx) const { + return fmt::formatter::format(magic_enum::detail::format_as(e), ctx); + } +}; + +#endif + +#endif // NEARGYE_MAGIC_ENUM_FORMAT_HPP diff --git a/build-config/magicenum/include/magic_enum/magic_enum_fuse.hpp b/build-config/magicenum/include/magic_enum/magic_enum_fuse.hpp new file mode 100644 index 0000000..ba570ff --- /dev/null +++ b/build-config/magicenum/include/magic_enum/magic_enum_fuse.hpp @@ -0,0 +1,94 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.8 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2026 Daniil Goncharov . +// +// 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 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. + +#ifndef NEARGYE_MAGIC_ENUM_FUSE_HPP +#define NEARGYE_MAGIC_ENUM_FUSE_HPP + +#include "magic_enum.hpp" + +namespace magic_enum { + +namespace detail { + +template +constexpr std::size_t fuse_bit_width() noexcept { + return log2((enum_count() << 1) - 1); +} + +template +constexpr optional fuse_one_enum(optional hash, E value) noexcept { + if (hash) { + if (const auto index = enum_index(value)) { + return (*hash << fuse_bit_width()) | *index; + } + } + return {}; +} + +template +constexpr optional fuse_enum(E value) noexcept { + return fuse_one_enum(0, value); +} + +template +constexpr optional fuse_enum(E head, Es... tail) noexcept { + return fuse_one_enum(fuse_enum(tail...), head); +} + +template +constexpr auto typesafe_fuse_enum(Es... values) noexcept { + enum class enum_fuse_t : std::uintmax_t; + const auto fuse = fuse_enum(values...); + if (fuse) { + return optional{static_cast(*fuse)}; + } + return optional{}; +} + +} // namespace magic_enum::detail + +// Returns a bijective mix of several enum values. This can be used to emulate 2D switch/case statements. +template +[[nodiscard]] constexpr auto enum_fuse(Es... values) noexcept { + static_assert((std::is_enum_v> && ...), "magic_enum::enum_fuse requires enum type."); + static_assert(sizeof...(Es) >= 2, "magic_enum::enum_fuse requires at least 2 values."); + static_assert((detail::fuse_bit_width>() + ...) <= (sizeof(std::uintmax_t) * 8), "magic_enum::enum_fuse does not work for large enums"); +#if defined(MAGIC_ENUM_NO_TYPESAFE_ENUM_FUSE) + const auto fuse = detail::fuse_enum...>(values...); +#else + const auto fuse = detail::typesafe_fuse_enum...>(values...); +#endif + return MAGIC_ENUM_ASSERT(fuse), fuse; +} + +} // namespace magic_enum + +#endif // NEARGYE_MAGIC_ENUM_FUSE_HPP diff --git a/build-config/magicenum/include/magic_enum/magic_enum_iostream.hpp b/build-config/magicenum/include/magic_enum/magic_enum_iostream.hpp new file mode 100644 index 0000000..4adf7c9 --- /dev/null +++ b/build-config/magicenum/include/magic_enum/magic_enum_iostream.hpp @@ -0,0 +1,117 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.8 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2026 Daniil Goncharov . +// +// 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 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. + +#ifndef NEARGYE_MAGIC_ENUM_IOSTREAM_HPP +#define NEARGYE_MAGIC_ENUM_IOSTREAM_HPP + +#include "magic_enum.hpp" +#include "magic_enum_flags.hpp" + +#ifndef MAGIC_ENUM_USE_STD_MODULE +# include +#endif + +namespace magic_enum { + +namespace ostream_operators { + +template = 0> +std::basic_ostream& operator<<(std::basic_ostream& os, E value) { + using D = std::decay_t; + using U = underlying_type_t; + + if constexpr (detail::supported::value) { + if constexpr (detail::subtype_v == detail::enum_subtype::flags) { + if (const auto name = enum_flags_name(value); !name.empty()) { + for (std::size_t i = 0; i < name.size(); ++i) { + os.put(name.data()[i]); + } + return os; + } + } else { + if (const auto name = enum_name(value); !name.empty()) { + for (std::size_t i = 0; i < name.size(); ++i) { + os.put(name.data()[i]); + } + return os; + } + } + } + return (os << static_cast(value)); +} + +template = 0> +std::basic_ostream& operator<<(std::basic_ostream& os, optional value) { + return value ? (os << *value) : os; +} + +} // namespace magic_enum::ostream_operators + +namespace istream_operators { + +template = 0> +std::basic_istream& operator>>(std::basic_istream& is, E& value) { + using D = std::decay_t; + + std::basic_string s; + is >> s; + if constexpr (detail::supported::value) { + if constexpr (detail::subtype_v == detail::enum_subtype::flags) { + if (const auto v = enum_flags_cast(s)) { + value = *v; + } else { + is.setstate(std::basic_ios::failbit); + } + } else { + if (const auto v = enum_cast(s)) { + value = *v; + } else { + is.setstate(std::basic_ios::failbit); + } + } + } else { + is.setstate(std::basic_ios::failbit); + } + return is; +} + +} // namespace magic_enum::istream_operators + +namespace iostream_operators { + +using magic_enum::ostream_operators::operator<<; +using magic_enum::istream_operators::operator>>; + +} // namespace magic_enum::iostream_operators + +} // namespace magic_enum + +#endif // NEARGYE_MAGIC_ENUM_IOSTREAM_HPP diff --git a/build-config/magicenum/include/magic_enum/magic_enum_switch.hpp b/build-config/magicenum/include/magic_enum/magic_enum_switch.hpp new file mode 100644 index 0000000..9da6613 --- /dev/null +++ b/build-config/magicenum/include/magic_enum/magic_enum_switch.hpp @@ -0,0 +1,201 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.8 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2026 Daniil Goncharov . +// +// 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 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. + +#ifndef NEARGYE_MAGIC_ENUM_SWITCH_HPP +#define NEARGYE_MAGIC_ENUM_SWITCH_HPP + +#include "magic_enum.hpp" + +namespace magic_enum { + +namespace detail { + +struct default_result_type {}; + +template +struct identity { + using type = T; +}; + +struct nonesuch {}; + +template > +struct invoke_result : identity {}; + +template +struct invoke_result : std::invoke_result {}; + +template +using invoke_result_t = typename invoke_result::type; + +template +constexpr auto common_invocable(std::index_sequence) noexcept { + static_assert(std::is_enum_v, "magic_enum::detail::invocable_index requires enum type."); + + if constexpr (count_v == 0) { + return identity{}; + } else { + return std::common_type[J]>>...>{}; + } +} + +template +constexpr auto result_type() noexcept { + static_assert(std::is_enum_v, "magic_enum::detail::result_type requires enum type."); + + constexpr auto seq = std::make_index_sequence>{}; + using R = std::decay_t(seq))::type>; + using D = std::decay_t; + if constexpr (std::is_same_v) { + if constexpr (std::is_same_v) { + return identity{}; + } else { + return identity{}; + } + } else { + if constexpr (std::is_convertible_v && (!HasResult || std::is_convertible_v)) { + return identity{}; + } else if constexpr (std::is_convertible_v) { + return identity{}; + } else { + return identity{}; + } + } +} + +template , typename R = typename decltype(result_type())::type> +using result_t = std::enable_if_t && !std::is_same_v, R>; + +template +using result_with_fallback_t = result_t; + +#if !defined(MAGIC_ENUM_ENABLE_HASH) && !defined(MAGIC_ENUM_ENABLE_HASH_SWITCH) + +template +inline constexpr auto default_result_type_lambda = []() noexcept(std::is_nothrow_default_constructible_v) { return T{}; }; + +template <> +inline constexpr auto default_result_type_lambda = []() noexcept {}; + +template +constexpr decltype(auto) linear_switch_impl(F&& f, E value, Def&& def) { + if constexpr (J < End) { + using V = enum_constant()>; + if (enum_value_equal(value, V::value)) { + if constexpr (std::is_invocable_r_v) { + return static_cast(detail::invoke_constant(std::forward(f), V{})); + } else { + return def(); + } + } else { + return linear_switch_impl(std::forward(f), value, std::forward(def)); + } + } else { + return def(); + } +} + +template +constexpr decltype(auto) linear_switch(F&& f, E value, Def&& def) { + static_assert(is_enum_v, "magic_enum::detail::linear_switch requires enum type."); + + if constexpr (count_v == 0) { + return def(); + } else { + return linear_switch_impl<0, count_v, R, E, S>(std::forward(f), value, std::forward(def)); + } +} +#endif + +} // namespace magic_enum::detail + +template , typename F, typename R = detail::result_t> +constexpr decltype(auto) enum_switch(F&& f, E value) { + using D = std::decay_t; + static_assert(std::is_enum_v, "magic_enum::enum_switch requires enum type."); + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + +#if defined(MAGIC_ENUM_ENABLE_HASH) || defined(MAGIC_ENUM_ENABLE_HASH_SWITCH) + return detail::hash_switch_values( + std::forward(f), + value, + detail::default_result_type_lambda, + [](D lhs, D rhs) { return detail::enum_value_equal(lhs, rhs); }); +#else + return detail::linear_switch( + std::forward(f), + value, + detail::default_result_type_lambda); +#endif +} + +template > +constexpr decltype(auto) enum_switch(F&& f, E value) { + return enum_switch(std::forward(f), value); +} + +template , typename F, typename R = detail::result_with_fallback_t> +constexpr decltype(auto) enum_switch(F&& f, E value, Result&& result) { + using D = std::decay_t; + static_assert(std::is_enum_v, "magic_enum::enum_switch requires enum type."); + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + +#if defined(MAGIC_ENUM_ENABLE_HASH) || defined(MAGIC_ENUM_ENABLE_HASH_SWITCH) + return detail::hash_switch_values( + std::forward(f), + value, + [&result]() -> R { return std::forward(result); }, + [](D lhs, D rhs) { return detail::enum_value_equal(lhs, rhs); }); +#else + return detail::linear_switch( + std::forward(f), + value, + [&result]() -> R { return std::forward(result); }); +#endif +} + +template > +constexpr decltype(auto) enum_switch(F&& f, E value, Result&& result) { + return enum_switch(std::forward(f), value, std::forward(result)); +} + +} // namespace magic_enum + +template <> +struct std::common_type : magic_enum::detail::identity {}; + +template +struct std::common_type : magic_enum::detail::identity {}; + +template +struct std::common_type : magic_enum::detail::identity {}; + +#endif // NEARGYE_MAGIC_ENUM_SWITCH_HPP diff --git a/build-config/magicenum/include/magic_enum/magic_enum_utility.hpp b/build-config/magicenum/include/magic_enum/magic_enum_utility.hpp new file mode 100644 index 0000000..8041e30 --- /dev/null +++ b/build-config/magicenum/include/magic_enum/magic_enum_utility.hpp @@ -0,0 +1,149 @@ +// __ __ _ ______ _____ +// | \/ | (_) | ____| / ____|_ _ +// | \ / | __ _ __ _ _ ___ | |__ _ __ _ _ _ __ ___ | | _| |_ _| |_ +// | |\/| |/ _` |/ _` | |/ __| | __| | '_ \| | | | '_ ` _ \ | | |_ _|_ _| +// | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| +// |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| +// __/ | https://github.com/Neargye/magic_enum +// |___/ version 0.9.8 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2026 Daniil Goncharov . +// +// 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 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. + +#ifndef NEARGYE_MAGIC_ENUM_UTILITY_HPP +#define NEARGYE_MAGIC_ENUM_UTILITY_HPP + +#include "magic_enum.hpp" + +#ifndef MAGIC_ENUM_USE_STD_MODULE +# include +#endif + +namespace magic_enum { + +namespace detail { + +template +using enum_for_each_result_t = std::decay_t[J]>>>; + +template +constexpr auto for_each(F&& f, std::index_sequence) { + constexpr bool has_void_return = (std::is_void_v[J]>>> || ...); + constexpr bool all_same_return = (std::is_same_v[0]>>, std::invoke_result_t[J]>>> && ...); + + if constexpr (has_void_return) { + (detail::invoke_constant(f, enum_constant[J]>{}), ...); + } else if constexpr (all_same_return) { + return std::array, sizeof...(J)>{{detail::invoke_constant(f, enum_constant[J]>{})...}}; + } else { + return std::tuple...>{detail::invoke_constant(f, enum_constant[J]>{})...}; + } +} + +template +constexpr bool all_invocable(std::index_sequence) { + if constexpr (count_v == 0) { + return false; + } else { + return (std::is_invocable_v[J]>> && ...); + } +} + +} // namespace magic_enum::detail + +template , typename F, detail::enable_if_t = 0> +constexpr auto enum_for_each(F&& f) { + using D = std::decay_t; + static_assert(std::is_enum_v, "magic_enum::enum_for_each requires enum type."); + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + constexpr auto sep = std::make_index_sequence>{}; + + if constexpr (detail::all_invocable(sep)) { + return detail::for_each(std::forward(f), sep); + } else { + static_assert(detail::always_false_v, "magic_enum::enum_for_each requires invocable of all enum value."); + } +} + +template > +[[nodiscard]] constexpr auto enum_next_value(E value, std::ptrdiff_t n = 1) noexcept -> detail::enable_if_t>> { + using D = std::decay_t; + constexpr std::ptrdiff_t count = detail::count_v; + + if (const auto i = enum_index(value)) { + const auto index = static_cast(*i); + if ((n > 0 && n >= count - index) || (n < 0 && n < -index)) { + return {}; + } + return enum_value(static_cast(index + n)); + } + return {}; +} + +template > +[[nodiscard]] constexpr auto enum_next_value_circular(E value, std::ptrdiff_t n = 1) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + constexpr std::ptrdiff_t count = detail::count_v; + + if (const auto i = enum_index(value)) { + auto index = (static_cast(*i) + (n % count)) % count; + if (index < 0) { + index += count; + } + return enum_value(static_cast(index)); + } + return MAGIC_ENUM_ASSERT(false), value; +} + +template > +[[nodiscard]] constexpr auto enum_prev_value(E value, std::ptrdiff_t n = 1) noexcept -> detail::enable_if_t>> { + using D = std::decay_t; + constexpr std::ptrdiff_t count = detail::count_v; + + if (const auto i = enum_index(value)) { + const auto index = static_cast(*i); + if ((n > 0 && n > index) || (n < 0 && n <= index - count)) { + return {}; + } + return enum_value(static_cast(index - n)); + } + return {}; +} + +template > +[[nodiscard]] constexpr auto enum_prev_value_circular(E value, std::ptrdiff_t n = 1) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + constexpr std::ptrdiff_t count = detail::count_v; + + if (const auto i = enum_index(value)) { + auto index = (static_cast(*i) - (n % count)) % count; + if (index < 0) { + index += count; + } + return enum_value(static_cast(index)); + } + return MAGIC_ENUM_ASSERT(false), value; +} + +} // namespace magic_enum + +#endif // NEARGYE_MAGIC_ENUM_UTILITY_HPP diff --git a/build-config/magicenum/meson.build b/build-config/magicenum/meson.build new file mode 100644 index 0000000..d68252b --- /dev/null +++ b/build-config/magicenum/meson.build @@ -0,0 +1,13 @@ +magic_enum_include = include_directories('include') + +magic_enum_args = [] + +if get_option('magic_enum_hash') + magic_enum_args += '-DMAGIC_ENUM_ENABLE_HASH' +endif + +magic_enum_dep = declare_dependency( + include_directories: magic_enum_include, + compile_args: magic_enum_args, +) + diff --git a/build-config/magicenum/meson_options.txt b/build-config/magicenum/meson_options.txt new file mode 100644 index 0000000..a043f3c --- /dev/null +++ b/build-config/magicenum/meson_options.txt @@ -0,0 +1,6 @@ +option( + 'hash', + type : 'boolean', + value : false, + description : 'Do hashing at build time - longer build times, but O(1) string lookup' +) diff --git a/build-config/meson.build b/build-config/meson.build index 965242b..27e21b6 100644 --- a/build-config/meson.build +++ b/build-config/meson.build @@ -1,2 +1,4 @@ subdir('mfem') +subdir('nameof') +subdir('magicenum') diff --git a/build-config/mfem/meson.build b/build-config/mfem/meson.build index 9090217..ce0a82d 100644 --- a/build-config/mfem/meson.build +++ b/build-config/mfem/meson.build @@ -325,49 +325,107 @@ if mfem_has_cuda message(cuda_check_result.stdout().strip()) endif system_mfem = disabler() +system_mfem_dependencies = [] mfem_provider = 'source bundle' + if effective_allow_preinstalled - system_candidate = dependency('mfem', version: '>=4.10', required: false, allow_fallback: false) - if system_candidate.found() - system_compatible = true + system_candidate = dependency( + 'mfem', + version: '>=4.10', + required: false, + allow_fallback: false, + ) + + if system_candidate.found() + system_compatible = true + system_candidate_dependencies = [system_candidate] + + if mfem_has_mpi + system_mpi = dependency( + 'mpi', + language: 'cpp', + required: false, + ) + + if system_mpi.found() + system_candidate_dependencies += [system_mpi] + else + system_compatible = false + endif + endif + foreach i : range(mfem_feature_names.length()) if mfem_feature_states[i] feature_macro = mfem_cmake_names[i] + macro_ok = cpp.compiles( - '#include \n#ifndef ' + feature_macro + '\n#error missing\n#endif\nint main(){return 0;}', - dependencies: system_candidate, + '#include \n' + + '#ifndef ' + feature_macro + '\n' + + '#error missing\n' + + '#endif\n' + + 'int main(){return 0;}', + dependencies: system_candidate_dependencies, name: 'system MFEM provides ' + feature_macro, ) + system_compatible = system_compatible and macro_ok endif endforeach - precision_macro = get_option('mfem_precision') == 'single' ? 'MFEM_USE_SINGLE' : 'MFEM_USE_DOUBLE' + + precision_macro = get_option('mfem_precision') == 'single' \ + ? 'MFEM_USE_SINGLE' \ + : 'MFEM_USE_DOUBLE' + system_compatible = system_compatible and cpp.compiles( - '#include \n#ifndef ' + precision_macro + '\n#error precision mismatch\n#endif\nint main(){return 0;}', - dependencies: system_candidate, + '#include \n' + + '#ifndef ' + precision_macro + '\n' + + '#error precision mismatch\n' + + '#endif\n' + + 'int main(){return 0;}', + dependencies: system_candidate_dependencies, name: 'system MFEM uses requested precision', ) + foreach i : range(mfem_feature_names.length()) feature_opt = get_option('mfem_' + mfem_feature_names[i]) feature_macro = mfem_cmake_names[i] + if feature_opt.disabled() macro_absent = cpp.compiles( - '#include \n#ifdef ' + feature_macro + '\n#error explicitly disabled\n#endif\nint main(){return 0;}', - dependencies: system_candidate, + '#include \n' + + '#ifdef ' + feature_macro + '\n' + + '#error explicitly disabled\n' + + '#endif\n' + + 'int main(){return 0;}', + dependencies: system_candidate_dependencies, name: 'system MFEM omits disabled ' + feature_macro, ) + system_compatible = system_compatible and macro_absent endif endforeach + + if system_compatible + system_compatible = cpp.compiles( + '#include \n' + + 'int main(){return 0;}', + dependencies: system_candidate_dependencies, + name: 'system MFEM public headers are consumable', + ) + endif + if system_compatible system_mfem = system_candidate + system_mfem_dependencies = system_candidate_dependencies mfem_provider = 'system' else - message('The system MFEM does not satisfy the selected feature set; using the pinned source bundle.') + message( + 'The system MFEM does not satisfy the selected feature set; ' + + 'using the pinned source bundle.' + ) endif endif endif - uses_unmodelled_system_tpl = false foreach feature_name : mfem_feature_names if ( @@ -399,7 +457,10 @@ mfem_build_target = [] mpi_launcher_from_dependency = false if system_mfem.found() - mfem_dep = system_mfem + mfem_dep = declare_dependency( + dependencies: system_mfem_dependencies, + version: system_mfem.version(), + ) else mfem_source = subproject('mfem').get_variable('mfem_source_dir') diff --git a/build-config/nameof/LICENSE b/build-config/nameof/LICENSE new file mode 100644 index 0000000..6f27b09 --- /dev/null +++ b/build-config/nameof/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 - 2026 Daniil Goncharov + +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 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. \ No newline at end of file diff --git a/build-config/nameof/include/nameof.hpp b/build-config/nameof/include/nameof.hpp new file mode 100644 index 0000000..82d3d14 --- /dev/null +++ b/build-config/nameof/include/nameof.hpp @@ -0,0 +1,1582 @@ +// _ _ __ _____ +// | \ | | / _| / ____|_ _ +// | \| | __ _ _ __ ___ ___ ___ | |_ | | _| |_ _| |_ +// | . ` |/ _` | '_ ` _ \ / _ \/ _ \| _| | | |_ _|_ _| +// | |\ | (_| | | | | | | __/ (_) | | | |____|_| |_| +// |_| \_|\__,_|_| |_| |_|\___|\___/|_| \_____| +// https://github.com/Neargye/nameof +// version 0.10.6 +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2016 - 2026 Daniil Goncharov . +// +// 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 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. + +#ifndef NEARGYE_NAMEOF_HPP +#define NEARGYE_NAMEOF_HPP + +#define NAMEOF_VERSION_MAJOR 0 +#define NAMEOF_VERSION_MINOR 10 +#define NAMEOF_VERSION_PATCH 6 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(NAMEOF_FORCE_COMPILER_SPECIFIC_REFLECTION) && defined(__cpp_impl_reflection) && __cpp_impl_reflection >= 202506L && defined(__cpp_expansion_statements) && __cpp_expansion_statements >= 202506L +# if defined(__has_include) +# if __has_include() +# include +# endif +# endif +# if defined(__cpp_lib_reflection) && __cpp_lib_reflection >= 202506L && defined(__cpp_lib_define_static) && __cpp_lib_define_static >= 202506L +# define NAMEOF_DETAIL_USE_STD_REFLECTION 1 +# endif +#endif + +#if !defined(NAMEOF_USING_ALIAS_STRING) +# include +#endif +#if !defined(NAMEOF_USING_ALIAS_STRING_VIEW) +# include +#endif + +#if __has_include() +# include +# include +# include +#endif + +#if defined(__clang__) +# pragma clang diagnostic push +# if __has_warning("-Wenum-constexpr-conversion") +# pragma clang diagnostic ignored "-Wenum-constexpr-conversion" +# endif +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 28020) // Code analysis false positive for bounds-checked enum value arrays. +# pragma warning(disable : 4514) // Unreferenced inline function has been removed. +#endif + +// Checks nameof_type compiler compatibility. +#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1910 +# undef NAMEOF_TYPE_SUPPORTED +# define NAMEOF_TYPE_SUPPORTED 1 +#endif + +// Checks nameof_type_rtti compiler compatibility. +#if defined(__clang__) +# if __has_feature(cxx_rtti) +# undef NAMEOF_TYPE_RTTI_SUPPORTED +# define NAMEOF_TYPE_RTTI_SUPPORTED 1 +# endif +#elif defined(__GNUC__) && __GNUC__ >= 9 +# if defined(__GXX_RTTI) +# undef NAMEOF_TYPE_RTTI_SUPPORTED +# define NAMEOF_TYPE_RTTI_SUPPORTED 1 +# endif +#elif defined(_MSC_VER) +# if defined(_CPPRTTI) +# undef NAMEOF_TYPE_RTTI_SUPPORTED +# define NAMEOF_TYPE_RTTI_SUPPORTED 1 +# endif +#endif + +// Checks nameof_member compiler compatibility. +#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && defined(_MSVC_LANG) && _MSVC_LANG >= 202002L +# undef NAMEOF_MEMBER_SUPPORTED +# define NAMEOF_MEMBER_SUPPORTED 1 +#endif + +// Checks nameof_pointer compiler compatibility. +#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && defined(_MSVC_LANG) && _MSVC_LANG >= 202002L +# undef NAMEOF_POINTER_SUPPORTED +# define NAMEOF_POINTER_SUPPORTED 1 +#endif + +// Checks nameof_enum compiler compatibility. +#if defined(NAMEOF_DETAIL_USE_STD_REFLECTION) || defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1910 +# undef NAMEOF_ENUM_SUPPORTED +# define NAMEOF_ENUM_SUPPORTED 1 +#endif + +// Checks nameof_enum compiler aliases compatibility. +#if defined(NAMEOF_DETAIL_USE_STD_REFLECTION) || defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1920 +# undef NAMEOF_ENUM_SUPPORTED_ALIASES +# define NAMEOF_ENUM_SUPPORTED_ALIASES 1 +#endif + +// Enum value must be greater than or equal to NAMEOF_ENUM_RANGE_MIN. By default, NAMEOF_ENUM_RANGE_MIN = -128. +// If you need another default minimum for all enum types, redefine the macro NAMEOF_ENUM_RANGE_MIN. +#if !defined(NAMEOF_ENUM_RANGE_MIN) +# define NAMEOF_ENUM_RANGE_MIN -128 +#endif + +// Enum value must be less than or equal to NAMEOF_ENUM_RANGE_MAX. By default, NAMEOF_ENUM_RANGE_MAX = 127. +// If you need another default maximum for all enum types, redefine the macro NAMEOF_ENUM_RANGE_MAX. +#if !defined(NAMEOF_ENUM_RANGE_MAX) +# define NAMEOF_ENUM_RANGE_MAX 127 +#endif + +namespace nameof { + +// If you need another string_view type, define the macro NAMEOF_USING_ALIAS_STRING_VIEW. +#if defined(NAMEOF_USING_ALIAS_STRING_VIEW) +NAMEOF_USING_ALIAS_STRING_VIEW +#else +using std::string_view; +#endif + +// If you need another string type, define the macro NAMEOF_USING_ALIAS_STRING. +#if defined(NAMEOF_USING_ALIAS_STRING) +NAMEOF_USING_ALIAS_STRING +#else +using std::string; +#endif + +namespace customize { + +// Compiler-specific enum reflection scans [min, max]. Redefine NAMEOF_ENUM_RANGE_MIN/MAX globally or specialize enum_range for a specific enum. +template +struct enum_range { + static_assert(std::is_enum_v, "nameof::customize::enum_range requires an enum type."); + inline static constexpr int min = NAMEOF_ENUM_RANGE_MIN; + inline static constexpr int max = NAMEOF_ENUM_RANGE_MAX; +}; + +// If you need custom enum names, specialize enum_name for that enum type. +template +constexpr string_view enum_name(E) noexcept { + static_assert(std::is_enum_v, "nameof::customize::enum_name requires an enum type."); + return string_view{""}; +} + +// If you need a custom type name, specialize type_name for that type. +template +constexpr string_view type_name() noexcept { + return string_view{""}; +} + +// If you need a custom member name, specialize member_name for that member. +template +constexpr string_view member_name() noexcept { + return string_view{""}; +} + +// If you need a custom pointer name, specialize pointer_name for that pointer. +template +constexpr string_view pointer_name() noexcept { + return string_view{""}; +} + +} // namespace nameof::customize + +template +class [[nodiscard]] cstring { + public: + using value_type = const char; + using size_type = std::uint16_t; + using difference_type = std::ptrdiff_t; + using pointer = const char*; + using const_pointer = const char*; + using reference = const char&; + using const_reference = const char&; + + using iterator = const char*; + using const_iterator = const char*; + + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + constexpr explicit cstring(string_view str) noexcept : cstring{check_size(str), std::make_integer_sequence{}} {} + + constexpr cstring() = delete; + + constexpr cstring(const cstring&) = default; + + constexpr cstring(cstring&&) = default; + + ~cstring() = default; + + cstring& operator=(const cstring&) = default; + + cstring& operator=(cstring&&) = default; + + [[nodiscard]] constexpr const_pointer data() const noexcept { return chars_; } + + [[nodiscard]] constexpr size_type size() const noexcept { return N; } + + [[nodiscard]] constexpr const_iterator begin() const noexcept { return data(); } + + [[nodiscard]] constexpr const_iterator end() const noexcept { return data() + size(); } + + [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return begin(); } + + [[nodiscard]] constexpr const_iterator cend() const noexcept { return end(); } + + [[nodiscard]] constexpr const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator{end()}; } + + [[nodiscard]] constexpr const_reverse_iterator rend() const noexcept { return const_reverse_iterator{begin()}; } + + [[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept { return rbegin(); } + + [[nodiscard]] constexpr const_reverse_iterator crend() const noexcept { return rend(); } + + [[nodiscard]] constexpr const_reference operator[](size_type i) const noexcept { return assert(i < size()), chars_[i]; } + + [[nodiscard]] constexpr const_reference front() const noexcept { return chars_[0]; } + + [[nodiscard]] constexpr const_reference back() const noexcept { return chars_[N - 1]; } + + [[nodiscard]] constexpr size_type length() const noexcept { return size(); } + + [[nodiscard]] constexpr bool empty() const noexcept { return false; } + + [[nodiscard]] constexpr int compare(string_view str) const noexcept { return string_view{data(), size()}.compare(str); } + + [[nodiscard]] constexpr const char* c_str() const noexcept { return data(); } + + [[nodiscard]] string str() const { return {data(), size()}; } + + [[nodiscard]] constexpr operator string_view() const& noexcept { return {data(), size()}; } + + [[nodiscard]] constexpr operator string_view() const&& noexcept = delete; + + [[nodiscard]] constexpr explicit operator const_pointer() const noexcept { return data(); } + + [[nodiscard]] explicit operator string() const { return {data(), size()}; } + + private: + [[nodiscard]] static constexpr string_view check_size(string_view str) noexcept { return assert(str.size() == N), str; } + + template + constexpr cstring(string_view str, std::integer_sequence) noexcept : chars_{str[J]..., '\0'} {} + + char chars_[static_cast(N) + 1]; +}; + +template <> +class [[nodiscard]] cstring<0> { + public: + using value_type = const char; + using size_type = std::uint16_t; + using difference_type = std::ptrdiff_t; + using pointer = const char*; + using const_pointer = const char*; + using reference = const char&; + using const_reference = const char&; + + using iterator = const char*; + using const_iterator = const char*; + + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + constexpr explicit cstring([[maybe_unused]] string_view str) noexcept { assert(str.empty()); } + + constexpr cstring() = default; + + constexpr cstring(const cstring&) = default; + + constexpr cstring(cstring&&) = default; + + ~cstring() = default; + + cstring& operator=(const cstring&) = default; + + cstring& operator=(cstring&&) = default; + + [[nodiscard]] constexpr const_pointer data() const noexcept { return chars_; } + + [[nodiscard]] constexpr size_type size() const noexcept { return 0; } + + [[nodiscard]] constexpr const_iterator begin() const noexcept { return data(); } + + [[nodiscard]] constexpr const_iterator end() const noexcept { return data() + size(); } + + [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return begin(); } + + [[nodiscard]] constexpr const_iterator cend() const noexcept { return end(); } + + [[nodiscard]] constexpr const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator{end()}; } + + [[nodiscard]] constexpr const_reverse_iterator rend() const noexcept { return const_reverse_iterator{begin()}; } + + [[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept { return rbegin(); } + + [[nodiscard]] constexpr const_reverse_iterator crend() const noexcept { return rend(); } + + [[nodiscard]] constexpr size_type length() const noexcept { return 0; } + + [[nodiscard]] constexpr bool empty() const noexcept { return true; } + + [[nodiscard]] constexpr int compare(string_view str) const noexcept { return string_view{}.compare(str); } + + [[nodiscard]] constexpr const char* c_str() const noexcept { return chars_; } + + [[nodiscard]] string str() const { return {data(), size()}; } + + [[nodiscard]] constexpr operator string_view() const& noexcept { return {data(), size()}; } + + [[nodiscard]] constexpr operator string_view() const&& noexcept = delete; + + [[nodiscard]] constexpr explicit operator const_pointer() const noexcept { return chars_; } + + [[nodiscard]] explicit operator string() const { return {data(), size()}; } + + private: + static constexpr char chars_[1] = {}; +}; + +template +[[nodiscard]] constexpr bool operator==(const cstring& lhs, string_view rhs) noexcept { + return lhs.compare(rhs) == 0; +} + +template +[[nodiscard]] constexpr bool operator==(const cstring& lhs, const cstring& rhs) noexcept { + if constexpr (N != M) { + return false; + } else { + return lhs.compare(string_view{rhs.data(), rhs.size()}) == 0; + } +} + +template +[[nodiscard]] constexpr bool operator==(string_view lhs, const cstring& rhs) noexcept { + return lhs.compare(rhs) == 0; +} + +template +[[nodiscard]] constexpr bool operator!=(const cstring& lhs, string_view rhs) noexcept { + return lhs.compare(rhs) != 0; +} + +template +[[nodiscard]] constexpr bool operator!=(const cstring& lhs, const cstring& rhs) noexcept { + return !(lhs == rhs); +} + +template +[[nodiscard]] constexpr bool operator!=(string_view lhs, const cstring& rhs) noexcept { + return lhs.compare(rhs) != 0; +} + +template +[[nodiscard]] constexpr bool operator>(const cstring& lhs, string_view rhs) noexcept { + return lhs.compare(rhs) > 0; +} + +template +[[nodiscard]] constexpr bool operator>(const cstring& lhs, const cstring& rhs) noexcept { + return lhs.compare(string_view{rhs.data(), rhs.size()}) > 0; +} + +template +[[nodiscard]] constexpr bool operator>(string_view lhs, const cstring& rhs) noexcept { + return lhs.compare(rhs) > 0; +} + +template +[[nodiscard]] constexpr bool operator>=(const cstring& lhs, string_view rhs) noexcept { + return lhs.compare(rhs) >= 0; +} + +template +[[nodiscard]] constexpr bool operator>=(const cstring& lhs, const cstring& rhs) noexcept { + return lhs.compare(string_view{rhs.data(), rhs.size()}) >= 0; +} + +template +[[nodiscard]] constexpr bool operator>=(string_view lhs, const cstring& rhs) noexcept { + return lhs.compare(rhs) >= 0; +} + +template +[[nodiscard]] constexpr bool operator<(const cstring& lhs, string_view rhs) noexcept { + return lhs.compare(rhs) < 0; +} + +template +[[nodiscard]] constexpr bool operator<(const cstring& lhs, const cstring& rhs) noexcept { + return lhs.compare(string_view{rhs.data(), rhs.size()}) < 0; +} + +template +[[nodiscard]] constexpr bool operator<(string_view lhs, const cstring& rhs) noexcept { + return lhs.compare(rhs) < 0; +} + +template +[[nodiscard]] constexpr bool operator<=(const cstring& lhs, string_view rhs) noexcept { + return lhs.compare(rhs) <= 0; +} + +template +[[nodiscard]] constexpr bool operator<=(const cstring& lhs, const cstring& rhs) noexcept { + return lhs.compare(string_view{rhs.data(), rhs.size()}) <= 0; +} + +template +[[nodiscard]] constexpr bool operator<=(string_view lhs, const cstring& rhs) noexcept { + return lhs.compare(rhs) <= 0; +} + +template +std::basic_ostream& operator<<(std::basic_ostream& os, const cstring& str) { + for (const auto c : str) { + os.put(c); + } + return os; +} + +namespace detail { + +constexpr bool is_name_char(char c) noexcept { + return (c >= '0' && c <= '9') || + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c == '_'); +} + +constexpr bool is_name_start(char c) noexcept { + return (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c == '_'); +} + +constexpr string_view pretty_name(string_view name, bool remove_suffix = true) noexcept { + if (name.size() >= 1 && (name[0] == '"' || name[0] == '\'')) { + return {}; // Narrow multibyte string literal. + } else if (name.size() >= 2 && name[0] == 'R' && (name[1] == '"' || name[1] == '\'')) { + return {}; // Raw string literal. + } else if (name.size() >= 2 && name[0] == 'L' && (name[1] == '"' || name[1] == '\'')) { + return {}; // Wide string literal. + } else if (name.size() >= 2 && name[0] == 'U' && (name[1] == '"' || name[1] == '\'')) { + return {}; // UTF-32 encoded string literal. + } else if (name.size() >= 2 && name[0] == 'u' && (name[1] == '"' || name[1] == '\'')) { + return {}; // UTF-16 encoded string literal. + } else if (name.size() >= 3 && name[0] == 'u' && name[1] == '8' && (name[2] == '"' || name[2] == '\'')) { + return {}; // UTF-8 encoded string literal. + } else if (name.size() >= 1 && (name[0] >= '0' && name[0] <= '9')) { + return {}; // Invalid name. + } + + for (std::size_t i = name.size(), h = 0, s = 0; i > 0; --i) { + if (name[i - 1] == ')') { + ++h; + ++s; + continue; + } else if (name[i - 1] == '(') { + if (h == 0) { + return {}; + } + --h; + ++s; + continue; + } + + if (h == 0) { + name.remove_suffix(s); + break; + } else { + ++s; + continue; + } + } + + std::size_t s = 0; + for (std::size_t i = name.size(), h = 0; i > 0; --i) { + if (name[i - 1] == '>') { + ++h; + ++s; + continue; + } else if (name[i - 1] == '<') { + if (h == 0) { + return {}; + } + --h; + ++s; + continue; + } + + if (h == 0) { + break; + } else { + ++s; + continue; + } + } + + for (std::size_t i = name.size() - s; i > 0; --i) { + if (!is_name_char(name[i - 1])) { + name.remove_prefix(i); + break; + } + } + if (remove_suffix) { + name.remove_suffix(s); + } + + if (!name.empty() && is_name_start(name[0])) { + return name; + } + + return {}; // Invalid name. +} + +#if defined(_MSC_VER) && !defined(__clang__) +constexpr string_view pretty_member_name(string_view signature) noexcept { + std::size_t template_begin = 0; + for (; template_begin < signature.size() && signature[template_begin] != '<'; ++template_begin) {} + if (template_begin == signature.size()) { + return {}; + } + + for (std::size_t i = template_begin + 1; i + 2 < signature.size(); ++i) { + if (signature[i] != ':' || signature[i + 1] != ':' || !is_name_start(signature[i + 2])) { + continue; + } + + const auto name_begin = i + 2; + auto name_end = name_begin + 1; + while (name_end < signature.size() && is_name_char(signature[name_end])) { + ++name_end; + } + + auto parameter_begin = name_end; + if (parameter_begin < signature.size() && signature[parameter_begin] == '<') { + std::size_t depth = 0; + do { + if (signature[parameter_begin] == '<') { + ++depth; + } else if (signature[parameter_begin] == '>') { + --depth; + } + ++parameter_begin; + } while (parameter_begin < signature.size() && depth > 0); + + if (depth > 0) { + return {}; + } + } + + if (parameter_begin < signature.size() && signature[parameter_begin] == '(') { + return string_view{signature.data() + name_begin, name_end - name_begin}; + } + } + + return {}; +} +#endif + +#if !defined(NAMEOF_DETAIL_USE_STD_REFLECTION) +constexpr bool enum_name_valid(string_view name) noexcept { +#if defined(__clang__) + constexpr auto anonymous_namespace_size = sizeof("(anonymous namespace)::") - 1; + while (name.size() > anonymous_namespace_size && + name[0] == '(' && + name[1] == 'a' && + name[10] == ' ' && + name[20] == ')' && + name[21] == ':' && + name[22] == ':') { + name.remove_prefix(anonymous_namespace_size); + } +#elif defined(__GNUC__) + constexpr auto gcc_anonymous_namespace_size = sizeof("{anonymous}::") - 1; + while (name.size() > gcc_anonymous_namespace_size && + name[0] == '{' && + name[1] == 'a' && + name[10] == '}' && + name[11] == ':' && + name[12] == ':') { + name.remove_prefix(gcc_anonymous_namespace_size); + } + constexpr auto unnamed_namespace_size = sizeof("::") - 1; + while (name.size() > unnamed_namespace_size && + name[0] == '<' && + name[1] == 'u' && + name[8] == '>' && + name[9] == ':' && + name[10] == ':') { + name.remove_prefix(unnamed_namespace_size); + } +#elif defined(_MSC_VER) + constexpr auto msvc_anonymous_namespace_size = sizeof("`anonymous-namespace'::") - 1; + while (name.size() > msvc_anonymous_namespace_size && + name[0] == '`' && + name[1] == 'a' && + name[10] == '-' && + name[20] == '\'' && + name[21] == ':' && + name[22] == ':') { + name.remove_prefix(msvc_anonymous_namespace_size); + } +#endif + + return !name.empty() && is_name_start(name[0]); +} +#endif + +template +using make_unsigned_t = std::make_unsigned_t, unsigned char, T>>; + +#if !defined(NAMEOF_DETAIL_USE_STD_REFLECTION) + +# if defined(__cpp_lib_array_constexpr) && __cpp_lib_array_constexpr >= 201603L +# define NAMEOF_ARRAY_CONSTEXPR 1 +# else +template +constexpr std::array, N> to_array(T (&a)[N], std::index_sequence) noexcept { + return {{a[J]...}}; +} +# endif + +template +constexpr bool cmp_less(L lhs, R rhs) noexcept { + static_assert(std::is_integral_v && std::is_integral_v, "nameof::detail::cmp_less requires integral types."); + + if constexpr (std::is_same_v && std::is_same_v) { + return static_cast(lhs) < static_cast(rhs); + } else if constexpr (std::is_same_v) { + return static_cast(lhs) < rhs; + } else if constexpr (std::is_same_v) { + return lhs < static_cast(rhs); + } else if constexpr (std::is_signed_v == std::is_signed_v) { + return lhs < rhs; + } else if constexpr (std::is_signed_v) { + using C = std::common_type_t, std::make_unsigned_t>; + return rhs > 0 && static_cast(lhs) < static_cast(rhs); + } else { + using C = std::common_type_t, std::make_unsigned_t>; + return lhs < 0 || static_cast(lhs) < static_cast(rhs); + } +} + +#endif + +template +struct nameof_enum_supported +#if defined(NAMEOF_ENUM_SUPPORTED) && NAMEOF_ENUM_SUPPORTED || defined(NAMEOF_ENUM_NO_CHECK_SUPPORT) + : std::true_type {}; +#else + : std::false_type {}; +#endif + +template +using remove_cvref_t = std::remove_cv_t>; + +template +using enable_if_enum_t = std::enable_if_t>, R>; + +template +inline constexpr bool is_enum_v = std::is_enum_v && std::is_same_v>; + +template +constexpr bool enum_value_equal(E lhs, E rhs) noexcept { + using U = std::underlying_type_t; + return static_cast(lhs) == static_cast(rhs); +} + +#if defined(NAMEOF_DETAIL_USE_STD_REFLECTION) + +namespace reflection { + +template +inline constexpr bool always_false_v = false; + +template +consteval auto enumerators() noexcept { + if constexpr (std::meta::is_enumerable_type(^^E)) { + return std::define_static_array(std::meta::enumerators_of(^^E)); + } else { + static_assert(always_false_v, "nameof requires a complete enum definition."); + return std::array{}; + } +} + +template +inline constexpr auto enumerators_v = enumerators(); + +template > +consteval auto enum_name() noexcept { + static_assert(std::is_enum_v, "nameof::detail::reflection::enum_name requires an enum value."); + + template for (constexpr auto enumerator : enumerators_v) { + if constexpr (enum_value_equal([:enumerator:], V)) { + constexpr auto identifier = std::meta::identifier_of(enumerator); + return string_view{identifier.data(), identifier.size()}; + } + } + return string_view{}; +} + +template +constexpr auto enum_name([[maybe_unused]] E value) noexcept { + template for (constexpr auto enumerator : enumerators_v) { + constexpr E candidate = [:enumerator:]; + if (enum_value_equal(candidate, value)) { + constexpr auto identifier = std::meta::identifier_of(enumerator); + constexpr auto persistent_name = std::define_static_string(identifier); + return string_view{persistent_name, identifier.size()}; + } + } + return string_view{""}; +} + +} // namespace reflection + +#endif + +template +constexpr auto n() noexcept { + static_assert(is_enum_v, "nameof::detail::n requires an enum type."); + + if constexpr (nameof_enum_supported::value) { +#if defined(NAMEOF_DETAIL_USE_STD_REFLECTION) + constexpr auto name = reflection::enum_name(); +#elif defined(__clang__) || defined(__GNUC__) + constexpr auto name = pretty_name({__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2}); +#elif defined(_MSC_VER) + constexpr auto name = pretty_name({__FUNCSIG__, sizeof(__FUNCSIG__) - 17}); +#else + constexpr auto name = string_view{""}; +#endif + return name; + } else { + return string_view{""}; + } +} + +#if !defined(NAMEOF_DETAIL_USE_STD_REFLECTION) +template +constexpr bool nv() noexcept { + using E = decltype(V); + static_assert(is_enum_v, "nameof::detail::nv requires an enum type."); + + if constexpr (nameof_enum_supported::value) { +#if defined(__GNUC__) && !defined(__clang__) + constexpr auto prefix = sizeof("constexpr bool nameof::detail::nv() [with auto V = ") - 1; + static_assert(sizeof(__PRETTY_FUNCTION__) > prefix + 2, "nameof::detail::nv requires a valid __PRETTY_FUNCTION__."); + return enum_name_valid({__PRETTY_FUNCTION__ + prefix, sizeof(__PRETTY_FUNCTION__) - prefix - 2}); +#elif defined(__clang__) + constexpr auto prefix = sizeof("bool nameof::detail::nv() [V = ") - 1; + static_assert(sizeof(__PRETTY_FUNCTION__) > prefix + 2, "nameof::detail::nv requires a valid __PRETTY_FUNCTION__."); + return enum_name_valid({__PRETTY_FUNCTION__ + prefix, sizeof(__PRETTY_FUNCTION__) - prefix - 2}); +#elif defined(_MSC_VER) + constexpr auto prefix = __FUNCSIG__[5] == 'c' ? sizeof("bool const __cdecl nameof::detail::nv<") - 1 : sizeof("bool __cdecl nameof::detail::nv<") - 1; + constexpr auto suffix = sizeof(">(void) noexcept") - 1; + static_assert(sizeof(__FUNCSIG__) > prefix + suffix + 1, "nameof::detail::nv requires a valid __FUNCSIG__."); + return enum_name_valid({__FUNCSIG__ + prefix, sizeof(__FUNCSIG__) - prefix - suffix - 1}); +#else + return false; +#endif + } else { + return false; + } +} +#endif + +template +constexpr auto enum_name() noexcept { + constexpr auto name = n(); + return cstring{name}; +} + +template +inline constexpr auto enum_name_v = enum_name(); + +template +constexpr auto custom_enum_name() noexcept { + constexpr auto name = customize::enum_name(V); + return cstring{name}; +} + +template +inline constexpr auto custom_enum_name_v = custom_enum_name(); + +#if !defined(NAMEOF_DETAIL_USE_STD_REFLECTION) +template +constexpr bool is_valid() noexcept { +#if defined(__clang__) && __clang_major__ >= 16 + // https://reviews.llvm.org/D130058, https://reviews.llvm.org/D131307 + constexpr E v = __builtin_bit_cast(E, V); +#else + constexpr E v = static_cast(V); +#endif + return nv(); +} + +template > +constexpr U ualue([[maybe_unused]] std::size_t i) noexcept { + if constexpr (IsFlags) { + if constexpr (std::is_same_v) { + return true; + } else { + return static_cast(U{1} << static_cast(static_cast(i) + O)); + } + } else { + return static_cast(static_cast(i) + O); + } +} + +template > +constexpr E value(std::size_t i) noexcept { + return static_cast(ualue(i)); +} + +template > +constexpr int reflected_min() noexcept { + if constexpr (IsFlags) { + return 0; + } else { + constexpr auto lhs = customize::enum_range::min; + constexpr auto rhs = (std::numeric_limits::min)(); + + if constexpr (cmp_less(rhs, lhs)) { + return lhs; + } else { + return rhs; + } + } +} + +template > +constexpr int reflected_max() noexcept { + if constexpr (IsFlags) { + return std::numeric_limits::digits - 1; + } else { + constexpr auto lhs = customize::enum_range::max; + constexpr auto rhs = (std::numeric_limits::max)(); + + if constexpr (cmp_less(lhs, rhs)) { + return lhs; + } else { + return rhs; + } + } +} + +#define NAMEOF_FOR_EACH_256(T) \ + T( 0)T( 1)T( 2)T( 3)T( 4)T( 5)T( 6)T( 7)T( 8)T( 9)T( 10)T( 11)T( 12)T( 13)T( 14)T( 15)T( 16)T( 17)T( 18)T( 19)T( 20)T( 21)T( 22)T( 23)T( 24)T( 25)T( 26)T( 27)T( 28)T( 29)T( 30)T( 31) \ + T( 32)T( 33)T( 34)T( 35)T( 36)T( 37)T( 38)T( 39)T( 40)T( 41)T( 42)T( 43)T( 44)T( 45)T( 46)T( 47)T( 48)T( 49)T( 50)T( 51)T( 52)T( 53)T( 54)T( 55)T( 56)T( 57)T( 58)T( 59)T( 60)T( 61)T( 62)T( 63) \ + T( 64)T( 65)T( 66)T( 67)T( 68)T( 69)T( 70)T( 71)T( 72)T( 73)T( 74)T( 75)T( 76)T( 77)T( 78)T( 79)T( 80)T( 81)T( 82)T( 83)T( 84)T( 85)T( 86)T( 87)T( 88)T( 89)T( 90)T( 91)T( 92)T( 93)T( 94)T( 95) \ + T( 96)T( 97)T( 98)T( 99)T(100)T(101)T(102)T(103)T(104)T(105)T(106)T(107)T(108)T(109)T(110)T(111)T(112)T(113)T(114)T(115)T(116)T(117)T(118)T(119)T(120)T(121)T(122)T(123)T(124)T(125)T(126)T(127) \ + T(128)T(129)T(130)T(131)T(132)T(133)T(134)T(135)T(136)T(137)T(138)T(139)T(140)T(141)T(142)T(143)T(144)T(145)T(146)T(147)T(148)T(149)T(150)T(151)T(152)T(153)T(154)T(155)T(156)T(157)T(158)T(159) \ + T(160)T(161)T(162)T(163)T(164)T(165)T(166)T(167)T(168)T(169)T(170)T(171)T(172)T(173)T(174)T(175)T(176)T(177)T(178)T(179)T(180)T(181)T(182)T(183)T(184)T(185)T(186)T(187)T(188)T(189)T(190)T(191) \ + T(192)T(193)T(194)T(195)T(196)T(197)T(198)T(199)T(200)T(201)T(202)T(203)T(204)T(205)T(206)T(207)T(208)T(209)T(210)T(211)T(212)T(213)T(214)T(215)T(216)T(217)T(218)T(219)T(220)T(221)T(222)T(223) \ + T(224)T(225)T(226)T(227)T(228)T(229)T(230)T(231)T(232)T(233)T(234)T(235)T(236)T(237)T(238)T(239)T(240)T(241)T(242)T(243)T(244)T(245)T(246)T(247)T(248)T(249)T(250)T(251)T(252)T(253)T(254)T(255) + +template +struct valid_count_t { + std::uint16_t count = 0; + std::uint16_t offsets[N] = {}; + + constexpr void set(std::size_t i) noexcept { + offsets[count++] = static_cast(i); + } +}; + +template +constexpr void valid_count(valid_count_t& vc) noexcept { +#define NAMEOF_ENUM_V(O) \ + if constexpr ((J + O) < Size) { \ + if constexpr (is_valid(J + O)>()) { \ + vc.set(J + O); \ + } \ + } + + NAMEOF_FOR_EACH_256(NAMEOF_ENUM_V) + + if constexpr ((J + 256) < Size) { + valid_count(vc); + } +#undef NAMEOF_ENUM_V +} + +template +constexpr auto valid_count() noexcept { + valid_count_t vc; + valid_count(vc); + return vc; +} + +template +constexpr auto values() noexcept { + constexpr auto vc = valid_count(); + + if constexpr (vc.count > 0) { +#if defined(NAMEOF_ARRAY_CONSTEXPR) + std::array values = {}; +#else + E values[vc.count] = {}; +#endif + if constexpr (vc.count == Size) { + for (std::size_t i = 0; i < vc.count; ++i) { + values[i] = value(i); + } + } else { + for (std::size_t i = 0; i < vc.count; ++i) { + values[i] = value(vc.offsets[i]); + } + } +#if defined(NAMEOF_ARRAY_CONSTEXPR) + return values; +#else + return to_array(values, std::make_index_sequence{}); +#endif + } else { + return std::array{}; + } +} + +template +constexpr auto values() noexcept { + constexpr auto min = reflected_min(); + constexpr auto max = reflected_max(); + constexpr bool valid_range = min <= max; + constexpr auto range_span = static_cast(max) - static_cast(min); + constexpr auto max_range_size = static_cast((std::numeric_limits::max)()); + constexpr bool valid_size = range_span < max_range_size - 1; + static_assert(valid_range, "nameof::customize::enum_range must contain at least one value."); + static_assert(!valid_range || valid_size, "nameof::customize::enum_range must contain fewer than UINT16_MAX values."); + + if constexpr (valid_range && valid_size) { + constexpr auto range_size = static_cast(range_span + 1); + return values(); + } else { + return std::array{}; + } +} + +template +inline constexpr auto values_v = values(); + +template +inline constexpr auto count_v = values_v.size(); + +template > +inline constexpr auto min_v = (count_v > 0) ? static_cast(values_v.front()) : U{0}; + +template > +inline constexpr auto max_v = (count_v > 0) ? static_cast(values_v.back()) : U{0}; + +template +constexpr auto names(std::index_sequence) noexcept { + constexpr auto names = std::array{{enum_name_v[J]>...}}; + return names; +} + +template +inline constexpr auto names_v = names(std::make_index_sequence>{}); + +template > +constexpr bool is_sparse() noexcept { + if constexpr (count_v == 0) { + return false; + } else if constexpr (std::is_same_v) { // bool special case + return false; + } else { + constexpr auto range_size = max_v - min_v + U{1}; + return range_size != count_v; + } +} + +template +inline constexpr bool is_sparse_v = is_sparse(); + +template >> +constexpr U flags_mask() noexcept { + U mask = 0; + for (const auto value : values_v) { + mask |= static_cast(value); + } + return mask; +} + +template +inline constexpr auto flags_mask_v = flags_mask(); + +#endif + +template +constexpr string_view enum_name(E value) noexcept { + if (auto custom_name = customize::enum_name(value); !custom_name.empty()) { + return custom_name; + } + +#if defined(NAMEOF_DETAIL_USE_STD_REFLECTION) + return reflection::enum_name(value); +#else + using U = std::underlying_type_t; + if constexpr (count_v > 0) { + if constexpr (is_sparse_v) { + for (std::size_t i = 0; i < count_v; ++i) { + if (enum_value_equal(values_v[i], value)) { + return names_v[i]; + } + } + } else { + const auto v = static_cast(value); + if (v >= min_v && v <= max_v) { + return names_v[static_cast(v - min_v)]; + } + } + } + return string_view{""}; +#endif +} + +template +constexpr string_view enum_flag_name(E value) noexcept { + if (auto custom_name = customize::enum_name(value); !custom_name.empty()) { + return custom_name; + } + +#if defined(NAMEOF_DETAIL_USE_STD_REFLECTION) + return reflection::enum_name(value); +#else + using U = make_unsigned_t>; + const auto flag = static_cast(value); + if ((flags_mask_v & flag) == U{0}) { + return {}; + } + for (std::size_t i = 0; i < count_v; ++i) { + if (static_cast(values_v[i]) == flag) { + return names_v[i]; + } + } + return {}; +#endif +} + +template +struct nameof_type_supported +#if defined(NAMEOF_TYPE_SUPPORTED) && NAMEOF_TYPE_SUPPORTED || defined(NAMEOF_TYPE_NO_CHECK_SUPPORT) + : std::true_type {}; +#else + : std::false_type {}; +#endif + +template +struct nameof_type_rtti_supported +#if defined(NAMEOF_TYPE_RTTI_SUPPORTED) && NAMEOF_TYPE_RTTI_SUPPORTED || defined(NAMEOF_TYPE_NO_CHECK_SUPPORT) + : std::true_type {}; +#else + : std::false_type {}; +#endif + +template +struct nameof_member_supported +#if defined(NAMEOF_MEMBER_SUPPORTED) && NAMEOF_MEMBER_SUPPORTED || defined(NAMEOF_TYPE_NO_CHECK_SUPPORT) + : std::true_type {}; +#else + : std::false_type {}; +#endif + +template +struct nameof_pointer_supported +#if defined(NAMEOF_POINTER_SUPPORTED) && NAMEOF_POINTER_SUPPORTED || defined(NAMEOF_TYPE_NO_CHECK_SUPPORT) + : std::true_type {}; +#else + : std::false_type {}; +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +template +struct identity { + using type = T; +}; +#else +template +using identity = T; +#endif + +template +using enable_if_has_short_name_t = std::enable_if_t> && !std::is_pointer_v>, R>; + +template +constexpr auto n() noexcept { +#if defined(_MSC_VER) && !defined(__clang__) + [[maybe_unused]] constexpr auto custom_name = customize::type_name(); +#else + [[maybe_unused]] constexpr auto custom_name = customize::type_name(); +#endif + + if constexpr (custom_name.empty() && nameof_type_supported::value) { +#if defined(__clang__) + constexpr string_view name{__PRETTY_FUNCTION__ + 31, sizeof(__PRETTY_FUNCTION__) - 34}; +#elif defined(__GNUC__) + constexpr string_view name{__PRETTY_FUNCTION__ + 46, sizeof(__PRETTY_FUNCTION__) - 49}; +#elif defined(_MSC_VER) + constexpr string_view name{__FUNCSIG__ + 63, sizeof(__FUNCSIG__) - 81 - (__FUNCSIG__[sizeof(__FUNCSIG__) - 19] == ' ' ? 1 : 0)}; +#else + constexpr auto name = string_view{""}; +#endif + return cstring{name}; + } else { + return cstring{custom_name}; + } +} + +template +inline constexpr auto type_name_v = n(); + +template +constexpr auto short_type_name() noexcept { + constexpr auto name = pretty_name(type_name_v); + static_assert(!name.empty(), "Type does not have a short name."); + return cstring{name}; +} + +template +inline constexpr auto short_type_name_v = short_type_name(); + +template +string full_type_name(string name) { + using U = std::remove_reference_t; + if constexpr (std::is_const_v || std::is_volatile_v) { + string qualified_name; + if constexpr (std::is_volatile_v) { + qualified_name.append("volatile ", 9); + } + if constexpr (std::is_const_v) { + qualified_name.append("const ", 6); + } + qualified_name.append(name); + name = std::move(qualified_name); + } + if constexpr (std::is_lvalue_reference_v) { + name.append(1, '&'); + } + if constexpr (std::is_rvalue_reference_v) { + name.append("&&", 2); + } + return name; +} + +#if __has_include() +using demangle_ptr = std::unique_ptr; + +struct demangled_name { + demangle_ptr p; + string_view v; + + [[nodiscard]] string_view view() const noexcept { return v; } + [[nodiscard]] bool empty() const noexcept { return v.empty(); } + [[nodiscard]] string str() const { return {v.data(), v.size()}; } +}; + +inline demangled_name demangle(const char* tn) { + assert(tn != nullptr); + if (tn == nullptr) { + return {demangle_ptr{nullptr, &std::free}, string_view{""}}; + } + int status = 0; + demangle_ptr p{abi::__cxa_demangle(tn, nullptr, nullptr, &status), &std::free}; + if (status != 0) { + p.reset(); + } + const auto v = p ? string_view{p.get()} : string_view{tn}; + return {std::move(p), v}; +} + +template +string nameof_type_rtti(const char* tn) { + static_assert(nameof_type_rtti_supported::value, "NAMEOF_TYPE_RTTI is not supported by this compiler (https://github.com/Neargye/nameof#compiler-compatibility)."); + const auto name = demangle(tn); + assert(!name.empty() && "Type does not have a name."); + return name.str(); +} + +template +string nameof_full_type_rtti(const char* tn) { + static_assert(nameof_type_rtti_supported::value, "NAMEOF_FULL_TYPE_RTTI is not supported by this compiler (https://github.com/Neargye/nameof#compiler-compatibility)."); + const auto name = demangle(tn); + assert(!name.empty() && "Type does not have a name."); + return full_type_name(name.str()); +} + +template = 0> +string nameof_short_type_rtti(const char* tn) { + static_assert(nameof_type_rtti_supported::value, "NAMEOF_SHORT_TYPE_RTTI is not supported by this compiler (https://github.com/Neargye/nameof#compiler-compatibility)."); + const auto full_name = demangle(tn); + const auto name = pretty_name(full_name.view()); + assert(!name.empty() && "Type does not have a short name."); + return {name.data(), name.size()}; +} +#else +template +string nameof_type_rtti(const char* tn) { + static_assert(nameof_type_rtti_supported::value, "NAMEOF_TYPE_RTTI is not supported by this compiler (https://github.com/Neargye/nameof#compiler-compatibility)."); + const auto name = string_view{tn != nullptr ? tn : ""}; + assert(!name.empty() && "Type does not have a name."); + return {name.data(), name.size()}; +} + +template +string nameof_full_type_rtti(const char* tn) { + static_assert(nameof_type_rtti_supported::value, "NAMEOF_FULL_TYPE_RTTI is not supported by this compiler (https://github.com/Neargye/nameof#compiler-compatibility)."); + const auto name = string_view{tn != nullptr ? tn : ""}; + assert(!name.empty() && "Type does not have a name."); + return full_type_name({name.data(), name.size()}); +} + +template = 0> +string nameof_short_type_rtti(const char* tn) { + static_assert(nameof_type_rtti_supported::value, "NAMEOF_SHORT_TYPE_RTTI is not supported by this compiler (https://github.com/Neargye/nameof#compiler-compatibility)."); + const auto name = pretty_name(tn != nullptr ? tn : ""); + assert(!name.empty() && "Type does not have a short name."); + return {name.data(), name.size()}; +} +#endif + +template +constexpr auto n() noexcept { + [[maybe_unused]] constexpr auto custom_name = customize::member_name(); + + if constexpr (custom_name.empty() && nameof_member_supported::value) { +#if defined(__clang__) || defined(__GNUC__) + constexpr auto name = pretty_name({__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2}); +#elif defined(_MSC_VER) && defined(_MSVC_LANG) && _MSVC_LANG >= 202002L + constexpr auto name = std::is_member_function_pointer_v + ? pretty_member_name({__FUNCSIG__, sizeof(__FUNCSIG__) - 1}) + : pretty_name({__FUNCSIG__, sizeof(__FUNCSIG__) - 18}); +#else + constexpr auto name = string_view{""}; +#endif + return cstring{name}; + } else { + return cstring{custom_name}; + } +} + +#if defined(__clang__) || defined(__GNUC__) +template +inline constexpr auto member_name_v = n(); +#elif defined(_MSC_VER) && defined(_MSVC_LANG) && _MSVC_LANG >= 202002L +template +From get_base_type(Type From::*); + +template +extern T nonexist_object; + +template +struct Store { + T v; +}; + +template +Store(T) -> Store; + +template +consteval auto get_member_name() noexcept { + if constexpr (std::is_member_function_pointer_v) { + return n(); + } else { + constexpr bool is_defined = sizeof(decltype(get_base_type(V))) != 0; + static_assert(is_defined, "nameof::nameof_member can only be used if the struct is fully defined. Use the NAMEOF macro, or separate the definition and declaration."); + if constexpr (is_defined) { + return n.*V)}>(); + } else { + return ""; + } + } +} + +template +inline constexpr auto member_name_v = get_member_name(); +#else +template +inline constexpr auto member_name_v = cstring<0>{}; +#endif + +template +struct is_same : std::false_type {}; + +template +struct is_same : std::true_type {}; + +template +constexpr bool is_nullptr_v = is_same>(nullptr)>::value; + +template +constexpr auto p() noexcept { + [[maybe_unused]] constexpr auto custom_name = customize::pointer_name().empty() && is_nullptr_v ? "nullptr" : customize::pointer_name(); + + if constexpr (custom_name.empty() && nameof_pointer_supported::value) { +#if defined(__clang__) + constexpr auto name = pretty_name({__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2}); +#elif defined(__GNUC__) + constexpr bool has_parenthesis = __PRETTY_FUNCTION__[sizeof(__PRETTY_FUNCTION__) - 3] == ')'; + constexpr auto name = pretty_name({__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2 - has_parenthesis}); +#elif defined(_MSC_VER) && defined(_MSVC_LANG) && _MSVC_LANG >= 202002L + constexpr auto name = pretty_name({__FUNCSIG__, sizeof(__FUNCSIG__) - 17}); +#else + constexpr auto name = string_view{""}; +#endif + return cstring{name}; + } else { + return cstring{custom_name}; + } +} + +template +inline constexpr auto pointer_name_v = p(); + +} // namespace nameof::detail + +// Checks whether nameof_type is supported by the compiler. +inline constexpr bool is_nameof_type_supported = detail::nameof_type_supported::value; + +// Checks whether nameof_type_rtti is supported by the compiler. +inline constexpr bool is_nameof_type_rtti_supported = detail::nameof_type_rtti_supported::value; + +// Checks whether nameof_member is supported by the compiler. +inline constexpr bool is_nameof_member_supported = detail::nameof_member_supported::value; + +// Checks whether nameof_pointer is supported by the compiler. +inline constexpr bool is_nameof_pointer_supported = detail::nameof_pointer_supported::value; + +// Checks whether nameof_enum is supported by the compiler. +inline constexpr bool is_nameof_enum_supported = detail::nameof_enum_supported::value; + +// Obtains name of enum value. +template +[[nodiscard]] constexpr auto nameof_enum(E value) noexcept -> detail::enable_if_enum_t { + using D = detail::remove_cvref_t; + static_assert(detail::nameof_enum_supported::value, "nameof::nameof_enum is not supported by this compiler (https://github.com/Neargye/nameof#compiler-compatibility)."); + return detail::enum_name(static_cast(value)); +} + +// Obtains name of enum value or default value if no name is available. +template +[[nodiscard]] auto nameof_enum_or(E value, string_view default_value) -> detail::enable_if_enum_t { + using D = detail::remove_cvref_t; + static_assert(detail::nameof_enum_supported::value, "nameof::nameof_enum_or is not supported by this compiler (https://github.com/Neargye/nameof#compiler-compatibility)."); + + if (auto v = nameof_enum(value); !v.empty()) { + return string{v.data(), v.size()}; + } + return string{default_value.data(), default_value.size()}; +} + +// Obtains name of enum flag value. +template +[[nodiscard]] auto nameof_enum_flag(E value, char sep = '|') -> detail::enable_if_enum_t { + using D = detail::remove_cvref_t; + using U = detail::make_unsigned_t>; + static_assert(detail::nameof_enum_supported::value, "nameof::nameof_enum_flag is not supported by this compiler (https://github.com/Neargye/nameof#compiler-compatibility)."); + + const auto flag_value = static_cast(value); + if (flag_value == U{0}) { + return {}; // Invalid value. + } + + string name; + auto remaining = flag_value; + while (remaining != U{0}) { + const auto flag = static_cast(remaining & static_cast(U{0} - remaining)); + const auto flag_name = detail::enum_flag_name(static_cast(flag)); + if (flag_name.empty()) { + return {}; // Unnamed flag. + } + if (!name.empty()) { + name.append(1, sep); + } + name.append(flag_name.data(), flag_name.size()); + remaining = static_cast(remaining ^ flag); + } + + return name; +} + +// Obtains name of enum value known at compile time. +// This version has a lower compile-time cost and is not restricted by the enum_range limitation. +template = 0> +[[nodiscard]] constexpr const auto& nameof_enum() noexcept { + using D = decltype(V); + static_assert(detail::nameof_enum_supported::value, "nameof::nameof_enum is not supported by this compiler (https://github.com/Neargye/nameof#compiler-compatibility)."); + constexpr auto custom_name = customize::enum_name(V); + if constexpr (custom_name.empty()) { + return detail::enum_name_v; + } else { + return detail::custom_enum_name_v; + } +} + +// Obtains type name; reference and cv-qualifiers are ignored. +template +[[nodiscard]] constexpr const auto& nameof_type() noexcept { + using U = detail::identity>; + static_assert(detail::nameof_type_supported::value, "nameof::nameof_type is not supported by this compiler (https://github.com/Neargye/nameof#compiler-compatibility)."); + return detail::type_name_v; +} + +// Obtains full type name with reference and cv-qualifiers. +template +[[nodiscard]] constexpr const auto& nameof_full_type() noexcept { + using U = detail::identity; + static_assert(detail::nameof_type_supported::value, "nameof::nameof_full_type is not supported by this compiler (https://github.com/Neargye/nameof#compiler-compatibility)."); + return detail::type_name_v; +} + +// Obtains short type name. +template = 0> +[[nodiscard]] constexpr const auto& nameof_short_type() noexcept { + using U = detail::identity>; + static_assert(detail::nameof_type_supported::value, "nameof::nameof_short_type is not supported by this compiler (https://github.com/Neargye/nameof#compiler-compatibility)."); + return detail::short_type_name_v; +} + +// Obtains name of member. +template , int> = 0> +[[nodiscard]] constexpr const auto& nameof_member() noexcept { + using U = decltype(V); + static_assert(detail::nameof_member_supported::value, "nameof::nameof_member is not supported by this compiler (https://github.com/Neargye/nameof#compiler-compatibility)."); + return detail::member_name_v; +} + +// Obtains name of a function, a global or class static variable. +template , int> = 0> +[[nodiscard]] constexpr const auto& nameof_pointer() noexcept { + using U = decltype(V); + static_assert(detail::nameof_pointer_supported::value, "nameof::nameof_pointer is not supported by this compiler (https://github.com/Neargye/nameof#compiler-compatibility)."); + return detail::pointer_name_v; +} + +} // namespace nameof + +#if __has_include() && ((defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) || __cplusplus >= 202002L) +# include + +# if defined(__cpp_lib_format) && __cpp_lib_format >= 201907L +template +struct std::formatter, char> : std::formatter { + template + auto format(const nameof::cstring& value, FormatContext& ctx) const { + return std::formatter::format(std::string_view{value.data(), value.size()}, ctx); + } +}; +# endif +#endif + +#if defined(FMT_VERSION) +template +struct fmt::formatter> : fmt::formatter { + template + auto format(const nameof::cstring& value, FormatContext& ctx) const { + return fmt::formatter::format(fmt::string_view{value.data(), value.size()}, ctx); + } +}; +#endif + +// Obtains name of variable, function, macro. +#define NAMEOF(...) []() constexpr noexcept { \ + ::std::void_t(); \ + constexpr auto _name = ::nameof::detail::pretty_name(#__VA_ARGS__); \ + static_assert(!_name.empty(), "Expression does not have a name."); \ + constexpr auto _size = _name.size(); \ + constexpr auto _nameof = ::nameof::cstring<_size>{_name}; \ + return _nameof; }() + +// Obtains full name of variable, function, macro. +#define NAMEOF_FULL(...) []() constexpr noexcept { \ + ::std::void_t(); \ + constexpr auto _name = ::nameof::detail::pretty_name(#__VA_ARGS__, false); \ + static_assert(!_name.empty(), "Expression does not have a name."); \ + constexpr auto _size = _name.size(); \ + constexpr auto _nameof_full = ::nameof::cstring<_size>{_name}; \ + return _nameof_full; }() + +// Obtains raw expression text. +#define NAMEOF_RAW(...) []() constexpr noexcept { \ + ::std::void_t(); \ + constexpr auto _name = ::nameof::string_view{#__VA_ARGS__}; \ + static_assert(!_name.empty(), "Expression does not have a name."); \ + constexpr auto _size = _name.size(); \ + constexpr auto _nameof_raw = ::nameof::cstring<_size>{_name}; \ + return _nameof_raw; }() + +// Obtains name of enum value. +#define NAMEOF_ENUM(...) ::nameof::nameof_enum(__VA_ARGS__) + +// Obtains name of enum value or default value if no name is available. +#define NAMEOF_ENUM_OR(...) ::nameof::nameof_enum_or(__VA_ARGS__) + +// Obtains name of enum value known at compile time. +// This version has a lower compile-time cost and is not restricted by the enum_range limitation. +#define NAMEOF_ENUM_CONST(...) ::nameof::nameof_enum<__VA_ARGS__>() + +// Obtains name of enum flag value. +#define NAMEOF_ENUM_FLAG(...) ::nameof::nameof_enum_flag(__VA_ARGS__) + +// Obtains type name; reference and cv-qualifiers are ignored. +#define NAMEOF_TYPE(...) ::nameof::nameof_type<__VA_ARGS__>() + +// Obtains full type name with reference and cv-qualifiers. +#define NAMEOF_FULL_TYPE(...) ::nameof::nameof_full_type<__VA_ARGS__>() + +// Obtains short type name. +#define NAMEOF_SHORT_TYPE(...) ::nameof::nameof_short_type<__VA_ARGS__>() + +// Obtains type name of expression; reference and cv-qualifiers are ignored. +#define NAMEOF_TYPE_EXPR(...) ::nameof::nameof_type() + +// Obtains full type name of expression with reference and cv-qualifiers. +#define NAMEOF_FULL_TYPE_EXPR(...) ::nameof::nameof_full_type() + +// Obtains short type name of expression. +#define NAMEOF_SHORT_TYPE_EXPR(...) ::nameof::nameof_short_type() + +// Obtains type name using RTTI. +#define NAMEOF_TYPE_RTTI(...) ::nameof::detail::nameof_type_rtti<::std::void_t>(typeid(__VA_ARGS__).name()) + +// Obtains full type name using RTTI. +#define NAMEOF_FULL_TYPE_RTTI(...) ::nameof::detail::nameof_full_type_rtti(typeid(__VA_ARGS__).name()) + +// Obtains short type name using RTTI. +#define NAMEOF_SHORT_TYPE_RTTI(...) ::nameof::detail::nameof_short_type_rtti(typeid(__VA_ARGS__).name()) + +// Obtains name of member. +#define NAMEOF_MEMBER(...) ::nameof::nameof_member<__VA_ARGS__>() + +// Obtains name of a function, a global or class static variable. +#define NAMEOF_POINTER(...) ::nameof::nameof_pointer<__VA_ARGS__>() + +#undef NAMEOF_ARRAY_CONSTEXPR +#undef NAMEOF_FOR_EACH_256 +#undef NAMEOF_DETAIL_USE_STD_REFLECTION + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +#endif // NEARGYE_NAMEOF_HPP \ No newline at end of file diff --git a/build-config/nameof/meson.build b/build-config/nameof/meson.build new file mode 100644 index 0000000..9b05276 --- /dev/null +++ b/build-config/nameof/meson.build @@ -0,0 +1,5 @@ +nameof_include_dir = include_directories('include') + +nameof_dep = declare_dependency( + include_directories: nameof_include_dir, +) \ No newline at end of file diff --git a/meson.build b/meson.build index b1d55b8..98ea045 100644 --- a/meson.build +++ b/meson.build @@ -10,6 +10,13 @@ project( ], ) +# these silence some warnings from mfem. Really these should be specific to mfem. +add_global_arguments('-Wno-unused', language: 'c') +add_global_arguments('-Wno-unused', language: 'cpp') +add_global_arguments('-Wno-unused-parameter', language: 'c') +add_global_arguments('-Wno-unused-parameter', language: 'cpp') + + python_mod = import('python') fs = import('fs') cc = meson.get_compiler('c') diff --git a/meson_options.txt b/meson_options.txt index 40bde6f..395eaa6 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -2,6 +2,7 @@ option('allow_preinstalled', type: 'boolean', value: true, description: 'Allow o option('feature_profile', type: 'combo', choices: ['minimal', 'portable', 'full'], value: 'portable', description: 'Defaults for auto features. full is strict and requires all selected non-bundled TPLs in dependency_prefix.') option('build_python', type: 'boolean', value: false, description: 'Build the nanobind Python extension and install native assets inside the Python package.') option('build_tests', type: 'boolean', value: true, description: 'Register serial, parallel, and Python smoke tests when applicable.') +option('build_checks', type: 'boolean', value: true, description: 'Build the compile-time checks for SERiF type invariants.') option('install_mfem', type: 'boolean', value: true, description: 'Install the source-built MFEM bundle and its runtime dependencies.') option('dependency_prefix', type: 'string', value: '', description: 'Explicit prefix containing optional TPLs. Used even when allow_preinstalled=false.') option('jobs', type: 'integer', min: 1, max: 1024, value: 4, description: 'Parallel jobs for the CMake/Make source bundle.') @@ -9,7 +10,9 @@ option('mfem_cuda_arch', type: 'string', value: 'native', description: 'CUDA arc option('mfem_hip_arch', type: 'string', value: '', description: 'HIP GPU target, for example gfx90a. Empty lets the toolchain choose.') option('mfem_precision', type: 'combo', choices: ['double', 'single'], value: 'double', description: 'Floating-point precision used consistently by MFEM and Hypre.') option('python_install_native', type: 'boolean', value: true, description: 'Install MFEM headers, CMake/pkg-config data, and runtime libraries under the Python package.') -option('macos_deployment_target', type: 'string', value: '15.0', description: 'Minimum macOS version for native artifacts (15.0 supports C++23 std::print); ignored on other systems. Empty uses the toolchain default.') +option('macos_deployment_target', type: 'string', value: '', description: 'Minimum macOS version for native artifacts (15.0 supports C++23 std::print); ignored on other systems. Empty uses the toolchain default.') + +option('magic_enum_hash', type: 'boolean', value: false, description: 'Enable hash support in magic_enum. This is a compile-time option that enables std::hash for magic_enum types.') option('mfem_mpi', type: 'feature', value: 'auto', description: 'MPI parallel MFEM; implies Hypre.') option('mfem_metis', type: 'feature', value: 'auto', description: 'METIS graph partitioning.') diff --git a/src/compile_time_checks/serif/discretization/blocks/fields.cpp b/src/compile_time_checks/serif/discretization/blocks/fields.cpp new file mode 100644 index 0000000..2e76e91 --- /dev/null +++ b/src/compile_time_checks/serif/discretization/blocks/fields.cpp @@ -0,0 +1,28 @@ +#include "serif/discretization/blocks/concepts.hpp" +#include "serif/discretization/blocks/traits.hpp" +#include "serif/dimensions/dimensions.hpp" + +namespace { + using namespace serif::discretization; + + using Density = blocks::density::mass; + using Displacement = blocks::displacement::vector; + using Coefficients = blocks::surface_deformation::coefficients; + + static_assert(blocks::SpatialTerm, "Density should be a spatial term"); + static_assert(blocks::SpatialTerm, "Displacement should be a spatial term"); + static_assert(!blocks::SpatialTerm, "Coefficients should not be a spatial term"); + + static_assert(blocks::HasQuantity, "Density should have a quantity"); + static_assert(blocks::HasValueBlock, "Coefficients should have a value block"); + static_assert(!blocks::HasResidualBlock, "Coefficients should not have a residual block"); + + using DensitySpace = blocks::FunctionalSpaceOf; + + static_assert(DensitySpace::functional_space.family == functional_space::Family::l2, "Density should be in an L2 space"); + + static_assert(DensitySpace::order == 2, "Density should be in a second order space"); + static_assert(DensitySpace::rank::value == 0, "Density should be a scalar field"); + static_assert(std::same_as, domain::StellarDomains>, "Density should be supported on stellar domains"); + static_assert(std::same_as, serif::dimensions::Density>, "Density should have a quantity of type Density"); +} diff --git a/src/compile_time_checks/serif/discretization/forms/forms.cpp b/src/compile_time_checks/serif/discretization/forms/forms.cpp new file mode 100644 index 0000000..c939fb3 --- /dev/null +++ b/src/compile_time_checks/serif/discretization/forms/forms.cpp @@ -0,0 +1,26 @@ +#include + +#include "serif/discretization/forms/runtime.hpp" +#include "serif/discretization/forms/concept.hpp" +#include "serif/discretization/forms/forms.hpp" + +// These are left in an anonymous namespace to avoid polluting the global namespace with these symbols at link time. +namespace { + using namespace serif::discretization::forms; + using namespace serif::discretization::blocks; + + // Note we define custom versions of forms that likely exist in the codebase. We are not here testing that the + // forms have a particular structure; rather, we are testing that given some known structure + // the form system behaves as we expect it to. + using HypotheticalTestingGravityMass = mass; + using HypotheticalTestingGravityDivergence = divergence; + + static_assert(Form, "GravityMass should satisfy the Form concept. A failure here indicates that the mass form is not properly defined for the gravity field. Please check out the forms submodule"); + static_assert(Form, "GravityDivergence should satisfy the Form concept. A failure here indicates that the divergence form is not properly defined for the gravity field. Please check out the forms submodule"); + + static_assert(std::same_as, "GravityMass::test_type should be gravity::gradient. A failure here indicates that the mass form is not properly defined for the gravity field. Please check out the forms submodule"); + static_assert(std::same_as, "GravityMass::trial_type should be gravity::gradient. A failure here indicates that the mass form is not properly defined for the gravity field. Please check out the forms submodule"); + + static_assert(std::same_as, "GravityDivergence::trial_type should be gravity::gradient. A failure here indicates that the divergence form is not properly defined for the gravity field. Please check out the forms submodule"); + static_assert(std::same_as, "GravityDivergence::test_type should be gravity::potential. A failure here indicates that the divergence form is not properly defined for the gravity field. Please check out the forms submodule"); +} \ No newline at end of file diff --git a/src/compile_time_checks/serif/discretization/forms/operands.cpp b/src/compile_time_checks/serif/discretization/forms/operands.cpp new file mode 100644 index 0000000..7f23d5b --- /dev/null +++ b/src/compile_time_checks/serif/discretization/forms/operands.cpp @@ -0,0 +1,23 @@ +#include "serif/discretization/forms/operands.hpp" + +#include +#include + +namespace { + using namespace serif::discretization; + + using DensityValue = forms::Operand; + using GravityValue = forms::Operand; + using GravityDivergence = forms::Operand; + using GravityNormalTrace = forms::Operand; + using DisplacementGradient = forms::Operand; + + static_assert(forms::IsOperand, "DensityValue should be a valid operand"); + static_assert(forms::IsOperand, "GravityValue should be a valid operand"); + static_assert(forms::IsOperand, "GravityDivergence should be a valid operand"); + static_assert(forms::IsOperand, "GravityNormalTrace should be a valid operand"); + static_assert(forms::IsOperand, "DisplacementGradient should be a valid operand"); + + static_assert(std::same_as, "GravityDivergence should have the correct term type"); + static_assert(std::same_as, "GravityDivergence should have the correct operation type"); +} \ No newline at end of file diff --git a/src/compile_time_checks/serif/discretization/quadrature/backend/mfem/mfem_resolver.cpp b/src/compile_time_checks/serif/discretization/quadrature/backend/mfem/mfem_resolver.cpp new file mode 100644 index 0000000..36473e7 --- /dev/null +++ b/src/compile_time_checks/serif/discretization/quadrature/backend/mfem/mfem_resolver.cpp @@ -0,0 +1,26 @@ +#include "serif/discretization/quadrature/backend/mfem/mfem_resolver.hpp" +#include "serif/discretization/quadrature/backend/mfem/mfem_static_order.hpp" + +#include "serif/utils/error/errors.hpp" + +namespace { + using namespace serif::discretization; + using namespace serif::utils::errors; + using namespace quadrature::backend::mfem; + + using GravityMassForm = forms::mass; + using GravityDivergenceForm = forms::divergence; + using GravityBoundaryForm = forms::boundary_flux; + + using DensityProjectionForm = forms::projection; + + + // Note when using serif_assert (since it is a consteval function) it must be within + // a function (C++ does not allow arbitrary calls to consteval functions outside of a function context). This is why we have this function here. It is not called, but it is used to force the compiler to evaluate the consteval function and thus trigger the compile time checks. + consteval void compile_checks() { + serif::utils::errors::serif_assert == 6>(); + serif::utils::errors::serif_assert == 4>(); + serif::utils::errors::serif_assert == 4>(); + serif::utils::errors::serif_assert == 4>(); + } +} \ No newline at end of file diff --git a/src/include/serif/dimensions/quantities.hpp b/src/include/serif/dimensions/quantities.hpp index 6aeec59..a186cdb 100644 --- a/src/include/serif/dimensions/quantities.hpp +++ b/src/include/serif/dimensions/quantities.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "serif/utils/misc/std_helper/cleaning.hpp" #include "serif/utils/misc/concepts/numeric.hpp" @@ -114,6 +115,7 @@ namespace serif::dimensions { template concept QuantityValueType = IsQuantityValue>::value; + template struct QuantityOf; @@ -135,4 +137,7 @@ namespace serif::dimensions { struct QuantityAt> { using Type = std::tuple_element_t>; }; + + template + concept ThermodynamicQuantityValueType = QuantityValueType && ThermodynamicQuantityType>; } diff --git a/src/include/serif/dimensions/runtime/runtime.hpp b/src/include/serif/dimensions/runtime/runtime.hpp index a70421e..45015b4 100644 --- a/src/include/serif/dimensions/runtime/runtime.hpp +++ b/src/include/serif/dimensions/runtime/runtime.hpp @@ -7,9 +7,12 @@ namespace serif::dimensions::runtime { class ThermodynamicQuantityID final { public: - explicit constexpr ThermodynamicQuantityID(std::string_view name) noexcept; + explicit constexpr ThermodynamicQuantityID(std::string_view name) noexcept : m_name(name) {}; + + [[nodiscard]] constexpr std::string_view name() const noexcept { + return m_name; + } - [[nodiscard]] constexpr std::string_view name() const noexcept; [[nodiscard]] friend constexpr bool operator==(const ThermodynamicQuantityID &, const ThermodynamicQuantityID &) noexcept = default; private: std::string_view m_name; // We can use a string view here since the constructor is constexpr and the string view will be valid for the lifetime of the program. @@ -26,6 +29,8 @@ namespace serif::dimensions::runtime { std::span inputQuantities; std::uint64_t partialDerivativeMask; - [[nodiscard]] constexpr bool hasPartialDerivative(const std::size_t inputIndex) const noexcept; + [[nodiscard]] constexpr bool hasPartialDerivative(const std::size_t inputIndex) const noexcept { + return inputIndex < inputQuantities.size() && (partialDerivativeMask & (std::uint64_t{1} << inputIndex)) != 0; + } }; } \ No newline at end of file diff --git a/src/include/serif/discretization/blocks/concepts.hpp b/src/include/serif/discretization/blocks/concepts.hpp index 7b9637e..7ae876c 100644 --- a/src/include/serif/discretization/blocks/concepts.hpp +++ b/src/include/serif/discretization/blocks/concepts.hpp @@ -1 +1,65 @@ -#pragma once \ No newline at end of file +#pragma once + +#include "serif/discretization/blocks/fields.hpp" + +namespace serif::discretization::blocks { + template + concept Field = std::derived_from; + + template + concept Term = std::derived_from, term_base> && // a term must derive from term_base + requires {typename std::remove_cvref_t::field_type;} && // a term must have a field_type member + Field::field_type>; // That field type member must be a valid field + + template + concept HasFunctionalSpace = + Term && + requires { + typename std::remove_cvref_t::FunctionalSpace; + }; + + template + concept HasSupport = + Term && + requires { + typename std::remove_cvref_t::Support; + }; + + template + concept HasQuantity = + Term && + requires { + typename std::remove_cvref_t::Quantity; + }; + + template + concept HasValueBlock = + Term && + requires { + typename std::remove_cvref_t::value; + }; + + template + concept HasResidualBlock = + Term && + requires { + typename std::remove_cvref_t::residual; + }; + + template + concept SpatialTerm = + Term && + HasFunctionalSpace && + HasSupport; + + template + concept PhysicalSpatialTerm = + SpatialTerm && + HasQuantity; + + template + struct IsSpatialTerm : std::bool_constant> {}; + + template + struct IsPhysicalSpatialTerm : std::bool_constant> {}; +} \ No newline at end of file diff --git a/src/include/serif/discretization/blocks/fields.hpp b/src/include/serif/discretization/blocks/fields.hpp index 2ba0739..0059e63 100644 --- a/src/include/serif/discretization/blocks/fields.hpp +++ b/src/include/serif/discretization/blocks/fields.hpp @@ -1,65 +1,130 @@ #pragma once +#include -#include "base.hpp" +#include "serif/dimensions/dimensions.hpp" #include "serif/discretization/blocks/base.hpp" +#include "serif/discretization/functional_space/space.hpp" +#include "serif/discretization/domain/concepts.hpp" +#include "serif/discretization/domain/physical_domains.hpp" +#include "serif/utils/types/type_list.hpp" + +#include "serif/meta/names.hpp" namespace serif::discretization::blocks { struct field {}; - struct term {}; + struct term_base {}; + + template + struct term : term_base { + using field_type = FieldT; + }; struct density final : field { - struct mass final : term { + static constexpr std::string_view name = meta::type_name(); + + struct mass final : term { // Note the reading here is mass density (that is this is not in reference to the common FEM usage of mass as a term in a weak form) + static constexpr std::string_view name = meta::type_name(); + + using FunctionalSpace = functional_space::DiscreteFunctionalSpace; // second order scalar L2 space + using Support = domain::StellarDomains; + using Quantity = dimensions::Density; + static constexpr std::string_view symbol = "ρ"; + struct value final : value_block_base {}; struct residual final : residual_block_base {}; }; + static inline constexpr mass mass_density_term{}; - static inline constexpr mass mass_term{}; + using Terms = utils::types::TypeList; }; struct displacement final : field { - struct geometry final : term { + static constexpr std::string_view name = meta::type_name(); + + struct vector final : term { // Similarly this is the displacement vector + static constexpr std::string_view name = meta::type_name(); + + using FunctionalSpace = functional_space::DiscreteFunctionalSpace; // third order vector H1 space + using Support = domain::AllDomains; + using Quantity = dimensions::Length; + static constexpr std::string_view symbol = "d"; + struct value final : value_block_base {}; struct residual final : residual_block_base {}; }; + static inline constexpr vector vector_term{}; - static inline constexpr geometry geometry_term{}; + using Terms = utils::types::TypeList; }; - struct surface_deformation final : field { - struct parameters final : term { + struct surface_deformation final : field { // Surface deformation is a more general field that the others, it is not pointwise defined over the domain. Use concepts to constrain this as a non spatial field. + static constexpr std::string_view name = meta::type_name(); + + struct coefficients final : term { + static constexpr std::string_view name = meta::type_name(); + struct value final : value_block_base {}; }; - struct shape_equilibrium final : term { + struct shape_equilibrium final : term { + static constexpr std::string_view name = meta::type_name(); + struct residual final : residual_block_base {}; }; - - static inline constexpr parameters parameters_term{}; + static inline constexpr coefficients coefficients_term{}; static inline constexpr shape_equilibrium shape_equilibrium_term{}; + + using Terms = utils::types::TypeList; }; struct gravity final : field { - struct gradient final : term { + static constexpr std::string_view name = meta::type_name(); + + struct gradient final : term { + static constexpr std::string_view name = meta::type_name(); + + using FunctionalSpace = functional_space::DiscreteFunctionalSpace; + using Support = domain::AllDomains; + using Quantity = dimensions::Acceleration; + static constexpr std::string_view symbol = "∇φ"; + struct value final : value_block_base {}; struct residual final : residual_block_base {}; }; - struct potential final : term { + struct potential final : term { + static constexpr std::string_view name = meta::type_name(); + + using FunctionalSpace = functional_space::DiscreteFunctionalSpace; + using Support = domain::AllDomains; + using Quantity = dimensions::GravitationalPotential; + static constexpr std::string_view symbol = "φ"; + struct value final : value_block_base {}; struct residual final : residual_block_base {}; }; - static inline constexpr potential potential_term{}; static inline constexpr gradient gradient_term{}; + + using Terms = utils::types::TypeList; }; struct enthalpy final : field { - struct specific final : term { + static constexpr std::string_view name = meta::type_name(); + + struct specific final : term { + static constexpr std::string_view name = meta::type_name(); + + using FunctionalSpace = functional_space::DiscreteFunctionalSpace; + using Support = domain::StellarDomains; + using Quantity = dimensions::SpecificEnthalpy; + static constexpr std::string_view symbol = "h"; + struct value final : value_block_base { }; struct residual final : residual_block_base { }; }; - static inline constexpr specific specific_term{}; - }; + using Terms = utils::types::TypeList; + }; } diff --git a/src/include/serif/discretization/blocks/runtime.hpp b/src/include/serif/discretization/blocks/runtime.hpp new file mode 100644 index 0000000..9238057 --- /dev/null +++ b/src/include/serif/discretization/blocks/runtime.hpp @@ -0,0 +1,43 @@ +#pragma once +#include +#include "serif/discretization/blocks/concepts.hpp" + +namespace serif::discretization::blocks { + struct FieldDescriptor final { + std::string_view name; + + [[nodiscard]] friend constexpr bool operator==(const FieldDescriptor&, const FieldDescriptor&) noexcept = default; + }; + + struct TermDescriptor final { + FieldDescriptor field; + std::string_view name; + + [[nodiscard]] friend constexpr bool operator==(const TermDescriptor&, const TermDescriptor&) noexcept = default; + }; + + template + inline constexpr FieldDescriptor field_descriptor{.name = FieldT::name}; + + template + inline constexpr TermDescriptor term_descriptor{ + .field = field_descriptor, + .name = TermT::name + }; +} + +template <> +struct std::formatter : std::formatter { + template + auto format(const serif::discretization::blocks::FieldDescriptor& fd, FormatContext& ctx) const { + return std::formatter::format(fd.name, ctx); + } +}; + +template <> +struct std::formatter : std::formatter { + template + auto format(const serif::discretization::blocks::TermDescriptor& td, FormatContext& ctx) const { + return std::formatter::format(std::format("TermDescriptor(field={}, name={})", td.field, td.name), ctx); + } +}; \ No newline at end of file diff --git a/src/include/serif/discretization/blocks/traits.hpp b/src/include/serif/discretization/blocks/traits.hpp index 7b9637e..8cda26f 100644 --- a/src/include/serif/discretization/blocks/traits.hpp +++ b/src/include/serif/discretization/blocks/traits.hpp @@ -1 +1,32 @@ -#pragma once \ No newline at end of file +#pragma once + +#include + +#include "serif/discretization/blocks/concepts.hpp" + +namespace serif::discretization::blocks { + template + using FieldOf = std::remove_cvref_t::field_type; + + template + using FunctionalSpaceOf = std::remove_cvref_t::FunctionalSpace; + + template + using SupportOf = std::remove_cvref_t::Support; + + template + using QuantityOf = std::remove_cvref_t::Quantity; + + template + using TermsOf = FieldT::Terms; + + template + struct SpatialTermTraits { + using FunctionalSpace = TermT::FunctionalSpace; + using Support = TermT::Support; + + static constexpr auto family = FunctionalSpace::functionalSpace.family; + static constexpr int order = FunctionalSpace::order; + static constexpr std::size_t rank = FunctionalSpace::rank; + }; +} \ No newline at end of file diff --git a/src/include/serif/discretization/domain/concepts.hpp b/src/include/serif/discretization/domain/concepts.hpp index f336255..afe8107 100644 --- a/src/include/serif/discretization/domain/concepts.hpp +++ b/src/include/serif/discretization/domain/concepts.hpp @@ -1,6 +1,11 @@ #pragma once #include +#include +#include +#include +#include +#include #include "serif/discretization/domain/types.hpp" #include "serif/utils/misc/std_helper/cleaning.hpp" @@ -17,10 +22,33 @@ namespace serif::discretization::domain { concept IsBoundary = AIsBaseClassOfB; // A DomainSet is several volumes treated as one. StellarDomains - // (core + envelope) is the motivating example. + // (core + envelope) is a motivating example. template struct DomainSet { static constexpr std::size_t count = CountVariadicArguments(); + + static inline const std::array names{ + DomainTs::name... + }; + }; + + // Do not make the constructors of this class explicit. We want to be able to pass a single domain or a domain set to functions that take a DomainOrSet. + class DomainOrSet final { + public: + template + // ReSharper disable once CppNonExplicitConvertingConstructor + constexpr DomainOrSet(DomainT) noexcept : m_domains(&DomainT::name, 1) {} + + template + // ReSharper disable once CppNonExplicitConvertingConstructor + constexpr DomainOrSet(DomainSet) noexcept : m_domains(DomainSet::names) {} + + [[nodiscard]] constexpr std::span domains() const noexcept { + return m_domains; + } + + private: + std::span m_domains; }; template @@ -53,3 +81,25 @@ namespace serif::discretization::domain { template concept BoundaryIsIdentifiedBy = utils::misc::std_helper::SameType; } + +template <> +struct std::formatter : std::formatter { + template + auto format(const serif::discretization::domain::DomainOrSet& dos, FormatContext& ctx) const { + auto join = [&](std::span strings, std::string_view delimiter) { + return std::accumulate( + strings.begin(), + strings.end(), + std::string{}, + [delimiter](std::string acc, std::string_view s) { + if (!acc.empty()) { + acc += delimiter; + } + acc += s; + return acc; + } + ); + }; + return std::formatter::format(std::format("DomainOrSet(domains=[{}])", join(dos.domains(), ", ")), ctx); + } +}; diff --git a/src/include/serif/discretization/forms/concept.hpp b/src/include/serif/discretization/forms/concept.hpp new file mode 100644 index 0000000..f8198b8 --- /dev/null +++ b/src/include/serif/discretization/forms/concept.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "serif/discretization/forms/forms.hpp" + +namespace serif::discretization::forms { + template + concept Form = std::derived_from, form> && // A form must derive from form + requires { + typename std::remove_cvref_t::operand_types; // a form must define a type list of operand types for the trial and test terms + }; // We could consider strengthening this to enforce that the type list (operand_types) contains only operand types and that there is an entry for TrialT and TestT +} \ No newline at end of file diff --git a/src/include/serif/discretization/forms/form_traits.hpp b/src/include/serif/discretization/forms/form_traits.hpp new file mode 100644 index 0000000..aefac74 --- /dev/null +++ b/src/include/serif/discretization/forms/form_traits.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include "serif/discretization/forms/concept.hpp" +#include "serif/discretization/forms/forms.hpp" + +namespace serif::discretization::forms { + template
+ struct FormTraits { + static constexpr int trial_order_reduction = 0; + static constexpr int test_order_reduction = 0; + }; + + template + struct FormTraits> { + static constexpr int trial_order_reduction = 0; + static constexpr int test_order_reduction = 0; + }; + + template + struct FormTraits> { + static constexpr int trial_order_reduction = 1; + static constexpr int test_order_reduction = 0; + }; + + template + struct FormTraits> { + static constexpr int trial_order_reduction = 0; + static constexpr int test_order_reduction = 0; + }; + + template + struct FormTraits> { + static constexpr int trial_order_reduction = 0; + static constexpr int test_order_reduction = 0; + }; + + template + struct FormTraits> { + static constexpr int trial_order_reduction = 0; + static constexpr int test_order_reduction = 0; + }; + + template + struct FormTraits> { + static constexpr int trial_order_reduction = 0; + static constexpr int test_order_reduction = 0; + }; +} \ No newline at end of file diff --git a/src/include/serif/discretization/forms/forms.hpp b/src/include/serif/discretization/forms/forms.hpp new file mode 100644 index 0000000..14e886a --- /dev/null +++ b/src/include/serif/discretization/forms/forms.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include +#include + +#include "serif/discretization/blocks/concepts.hpp" +#include "serif/discretization/forms/operands.hpp" +#include "serif/utils/types/type_list.hpp" +#include "serif/meta/names.hpp" + +namespace serif::discretization::forms { + struct form {}; + + template + struct mass final : form { + static constexpr std::string_view name = meta::template_name(); + + using trial_type = TrialT; + using test_type = TestT; + + using operand_types = utils::types::TypeList, Operand>; + }; + + template + struct divergence final : form { + static constexpr std::string_view name = meta::template_name(); + + using trial_type = TrialT; + using test_type = TestT; + + using operand_types = utils::types::TypeList, Operand>; + }; + + template + struct source final : form { + static constexpr std::string_view name = meta::template_name(); + + using trial_type = TrialT; + using test_type = TestT; + + using operand_types = utils::types::TypeList, Operand>; + }; + + template + struct force final : form { + static constexpr std::string_view name = meta::template_name(); + + using trial_type = TrialT; + using test_type = TestT; + + using operand_types = utils::types::TypeList, Operand>; + }; + + template + struct boundary_flux final : form { + static constexpr std::string_view name = meta::template_name(); + + using trial_type = TrialT; + using test_type = TestT; + + using operand_types = utils::types::TypeList, Operand>; + }; + + template + struct projection final : form { + static constexpr std::string_view name = meta::template_name(); + + using trial_type = TrialT; + using test_type = TestT; + + using operand_types = utils::types::TypeList, Operand>; + }; +} \ No newline at end of file diff --git a/src/include/serif/discretization/forms/operands.hpp b/src/include/serif/discretization/forms/operands.hpp new file mode 100644 index 0000000..0eeb58f --- /dev/null +++ b/src/include/serif/discretization/forms/operands.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +#include "serif/discretization/blocks/concepts.hpp" + +namespace serif::discretization::forms { + struct Operation { + struct Value {}; + struct Gradient {}; + struct Divergence {}; + struct Curl {}; + struct NormalTrace {}; + }; + + template + concept IsOperation = + std::same_as, Operation::Value> || + std::same_as, Operation::Gradient> || + std::same_as, Operation::Divergence> || + std::same_as, Operation::Curl> || + std::same_as, Operation::NormalTrace>; + + template // We choose to default to the value operation because it is the most common. + struct Operand final { + using term_type = TermT; + using operation_type = OperationT; + }; + + template + concept IsOperand = requires { // Recall that requires expressions are not evaluated. What this checks is that it is valid syntax given the type to requires the term type and operation type. It does not check that the term type is a valid term or that the operation type is a valid operation. + typename std::remove_cvref_t::term_type; + typename std::remove_cvref_t::operation_type; } && + blocks::Term::term_type> && // Then here, now that we know we can safely ask for the term type we validate it is actually a valid term + IsOperation::operation_type>; // And here we validate that the operation type is actually a valid operation + +}; \ No newline at end of file diff --git a/src/include/serif/discretization/forms/runtime.hpp b/src/include/serif/discretization/forms/runtime.hpp new file mode 100644 index 0000000..5c16eed --- /dev/null +++ b/src/include/serif/discretization/forms/runtime.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include "serif/discretization/blocks/runtime.hpp" +#include "serif/discretization/forms/concept.hpp" + +namespace serif::discretization::forms { + struct FormDescriptor final { + std::string_view name; + + blocks::TermDescriptor trial; + blocks::TermDescriptor test; + + [[nodiscard]] friend constexpr bool operator==(const FormDescriptor&, const FormDescriptor&) noexcept = default; + }; + + template + inline constexpr FormDescriptor form_descriptor{ + .name = FormT::name, + .trial = blocks::term_descriptor, + .test = blocks::term_descriptor + }; + +} + +template <> +struct std::formatter : std::formatter { + template + auto format(const serif::discretization::forms::FormDescriptor& fd, FormatContext& ctx) const { + return std::formatter::format(std::format("FormDescriptor(name={}, trial={}, test={})", fd.name, fd.trial, fd.test), ctx); + } +}; \ No newline at end of file diff --git a/src/include/serif/discretization/functional_space/space.hpp b/src/include/serif/discretization/functional_space/space.hpp new file mode 100644 index 0000000..2bfb685 --- /dev/null +++ b/src/include/serif/discretization/functional_space/space.hpp @@ -0,0 +1,167 @@ +#pragma once +#include +#include + +#include "serif/meta/enums.hpp" + +namespace serif::discretization::functional_space { + template + struct Rank { + constexpr static std::size_t value = R; + }; + + enum class Family { + l2, + h1, + hdiv, + hcurl + }; + + inline static auto Family2StringMap = meta::enum_to_enum_string_map(); + inline static auto String2FamilyMap = meta::enum_to_string_enum_map(); + + enum class Continuity { + discontinuous, + continuous, + normal_continuous, + tangential_continuous + }; + + inline static auto Continuity2StringMap = meta::enum_to_enum_string_map(); + inline static auto String2ContinuityMap = meta::enum_to_string_enum_map(); + + enum class Trace { + none, + value, + normal, + tangential + }; + + inline static auto Trace2StringMap = meta::enum_to_enum_string_map(); + inline static auto String2TraceMap = meta::enum_to_string_enum_map(); + + enum class Mapping { + identity, + contravariant_piola, + covariant_piola + }; + + inline static auto Mapping2StringMap = meta::enum_to_enum_string_map(); + inline static auto String2MappingMap = meta::enum_to_string_enum_map(); + + struct SpaceDescriptor final { + const Family family; + const Continuity continuity; + const Trace trace; + const Mapping mapping; + }; + + inline static constexpr SpaceDescriptor L2{ + .family = Family::l2, + .continuity = Continuity::discontinuous, + .trace = Trace::none, + .mapping = Mapping::identity + }; + + inline static constexpr SpaceDescriptor H1{ + .family = Family::h1, + .continuity = Continuity::continuous, + .trace = Trace::value, + .mapping = Mapping::identity + }; + + inline static constexpr SpaceDescriptor Hdiv{ + .family = Family::hdiv, + .continuity = Continuity::normal_continuous, + .trace = Trace::normal, + .mapping = Mapping::contravariant_piola + }; + + inline static constexpr SpaceDescriptor Hcurl{ + .family = Family::hcurl, + .continuity = Continuity::tangential_continuous, + .trace = Trace::tangential, + .mapping = Mapping::covariant_piola + }; + + template + concept IsSpaceDescriptor = std::is_same_v; + + template + struct DiscreteFunctionalSpace { + static constexpr SpaceDescriptor functional_space = Space; + static constexpr int order = Order; + using rank = Rank; + }; +} + +template +struct std::formatter> : std::formatter { + template + auto format(const serif::discretization::functional_space::Rank& /*rank*/, FormatContext& ctx) const { + if (R == 0) { + return std::formatter::format("Scalar", ctx); + } else if (R == 1) { + return std::formatter::format("Vector", ctx); + } else if (R >=2) { + return std::formatter::format("Rank " + std::to_string(R) + " tensor", ctx); + } + } +}; + +template <> +struct std::formatter : std::formatter { + template + auto format(const serif::discretization::functional_space::SpaceDescriptor& space, FormatContext& ctx) const { + return std::formatter::format(std::format("SpaceDescriptor(family={}, continuity={}, trace={}, mapping={})", + serif::discretization::functional_space::Family2StringMap.at(space.family), + serif::discretization::functional_space::Continuity2StringMap.at(space.continuity), + serif::discretization::functional_space::Trace2StringMap.at(space.trace), + serif::discretization::functional_space::Mapping2StringMap.at(space.mapping)), ctx); + } +}; + +// formatter for DiscreteFunctionalSpace +template +struct std::formatter> : std::formatter { + template + auto format(const serif::discretization::functional_space::DiscreteFunctionalSpace& space, FormatContext& ctx) const { + return std::formatter::format(std::format("DiscreteFunctionalSpace(functional_space={}, order={}, rank={})", + space.functional_space, + space.order, + space.rank), ctx); + } +}; + +// formatter for each enum +template <> +struct std::formatter : std::formatter { + template + auto format(const serif::discretization::functional_space::Family& family, FormatContext& ctx) const { + return std::formatter::format(serif::discretization::functional_space::Family2StringMap.at(family), ctx); + } +}; + +template <> +struct std::formatter : std::formatter { + template + auto format(const serif::discretization::functional_space::Continuity& continuity, FormatContext& ctx) const { + return std::formatter::format(serif::discretization::functional_space::Continuity2StringMap.at(continuity), ctx); + } +}; + +template <> +struct std::formatter : std::formatter { + template + auto format(const serif::discretization::functional_space::Trace& trace, FormatContext& ctx) const { + return std::formatter::format(serif::discretization::functional_space::Trace2StringMap.at(trace), ctx); + } +}; + +template <> +struct std::formatter : std::formatter { + template + auto format(const serif::discretization::functional_space::Mapping& mapping, FormatContext& ctx) const { + return std::formatter::format(serif::discretization::functional_space::Mapping2StringMap.at(mapping), ctx); + } +}; \ No newline at end of file diff --git a/src/include/serif/discretization/quadrature/backend/mfem/concepts.hpp b/src/include/serif/discretization/quadrature/backend/mfem/concepts.hpp new file mode 100644 index 0000000..5b1b336 --- /dev/null +++ b/src/include/serif/discretization/quadrature/backend/mfem/concepts.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "mfem/fem/intrules.hpp" + +namespace serif::discretization::quadrature::backend::mfem { + template + concept HasReferenceSetIntegrationRule = requires (IntegratorT &integrator, const ::mfem::IntegrationRule &integration_rule) { + integrator.SetIntegrationRule(integration_rule); + }; + + template + concept HasPointerSetIntegrationRule = requires (IntegratorT &integrator, const ::mfem::IntegrationRule &integration_rule) { + integrator.SetIntRule(&integration_rule); + }; + + template + concept HasSetIntRule = requires (IntegratorT &integrator, const ::mfem::IntegrationRule &integration_rule) { + integrator.SetIntRule(integration_rule); + }; + + template + concept ConfigurableIntegrator = HasReferenceSetIntegrationRule || + HasPointerSetIntegrationRule || + HasSetIntRule; +} \ No newline at end of file diff --git a/src/include/serif/discretization/quadrature/backend/mfem/configure.hpp b/src/include/serif/discretization/quadrature/backend/mfem/configure.hpp new file mode 100644 index 0000000..99c1e55 --- /dev/null +++ b/src/include/serif/discretization/quadrature/backend/mfem/configure.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "mfem/fem/intrules.hpp" + +#include "serif/discretization/quadrature/backend/mfem/concepts.hpp" + +namespace serif::discretization::quadrature::backend::mfem { + template + void set_integration_rule(IntegratorT &integrator, const ::mfem::IntegrationRule &integration_rule) { + if constexpr (HasReferenceSetIntegrationRule) { + integrator.SetIntegrationRule(integration_rule); + } else if constexpr (HasPointerSetIntegrationRule) { + integrator.SetIntegrationRule(&integration_rule); + } else { + integrator.SetIntRule(&integration_rule); + } + } +} \ No newline at end of file diff --git a/src/include/serif/discretization/quadrature/backend/mfem/mfem_resolver.hpp b/src/include/serif/discretization/quadrature/backend/mfem/mfem_resolver.hpp new file mode 100644 index 0000000..1be94a8 --- /dev/null +++ b/src/include/serif/discretization/quadrature/backend/mfem/mfem_resolver.hpp @@ -0,0 +1,88 @@ +#pragma once +#include + +#include "mfem/fem/geom.hpp" +#include "mfem/fem/intrules.hpp" + +#include "serif/discretization/blocks/traits.hpp" +#include "serif/discretization/quadrature/rules.hpp" + +#include "serif/discretization/quadrature/backend/order.hpp" + +#include "serif/discretization/domain/concepts.hpp" +#include "serif/discretization/quadrature/policy.hpp" +#include "serif/discretization/quadrature/query.hpp" + +#include "serif/discretization/quadrature/backend/mfem/concepts.hpp" +#include "serif/discretization/quadrature/backend/mfem/mfem_static_order.hpp" + +// We may want to consider renaming this file to runtime to stick to convention. The reason +// I did not initially is that this is more specific than pure runtime projection of compile +// time invariants (which is what most of the other files named runtime.hpp do). Something to think +// about + +// Note also that you will see ::mfem throughout. This is an important syntax to use here since MFEM's +// code lives in the mfem namespace; however, we also have code living in an mfem namespace. +// Outside of translation units that include using namespace serif::discretization::quadrature::backend +// and outside of the serif::discretization::quadrature::backend::mfem namespace itself these two things +// do not conflict. However, in those two cases there is a degeneracy the compiler cannot +// resolve between mfem:: and mfem:: (see what I did there?) Using ::mfem explicitly tells the compiler +// that we are looking from the root of the namespace tree not the current namespace. This is important to avoid ambiguity and compiler errors. + +namespace serif::discretization::quadrature::backend::mfem { + struct MFEMQuadratureContext final { + QuadratureRole role = QuadratureRole::discretization; + + ::mfem::Geometry::Type geometry = ::mfem::Geometry::INVALID; + int geometry_weight_order = 0; + + std::span dynamic_orders{}; + domain::DomainOrSet domain = domain::AllDomains{}; + + MappingKind mapping = MappingKind::none; + }; + + struct MFEMRule final { + Resolution resolution; + + const ::mfem::IntegrationRule *integration_rule = nullptr; + }; + + class MFEMResolver final { + public: + explicit MFEMResolver (Policy policy) noexcept : m_policy(std::move(policy)) {} // Do we actually want to use move rvalue semantics here? + + template + [[nodiscard]] MFEMRule resolve(const MFEMQuadratureContext& context) const { + if (context.geometry_weight_order < 0) { + utils::errors::serif_error("MFEM geometry weight order must be non-negative. Received an order of {}", context.geometry_weight_order); + } + + const int dynamic_order = sum_dynamic_orders(context.dynamic_orders); + const int base_order = mfem_form_static_order + dynamic_order + context.geometry_weight_order; + + const Query query { + .form = forms::form_descriptor, + .role = context.role, + .domain = context.domain, + .mapping = context.mapping, + .base_order = base_order + }; + + const Resolution resolution = m_policy.resolve(query); + + const ::mfem::IntegrationRule &integration_rule = ::mfem::IntRules.Get(context.geometry, resolution.order); + return MFEMRule{.resolution = resolution, .integration_rule = &integration_rule}; + } + + template + Resolution configure(IntegratorT &integrator, const MFEMQuadratureContext& context) const { + const MFEMRule rule = resolve(context); + set_integration_rule(integrator, *rule.integration_rule); + return rule.resolution; + } + + private: + Policy m_policy; + }; +} \ No newline at end of file diff --git a/src/include/serif/discretization/quadrature/backend/mfem/mfem_static_order.hpp b/src/include/serif/discretization/quadrature/backend/mfem/mfem_static_order.hpp new file mode 100644 index 0000000..0edb883 --- /dev/null +++ b/src/include/serif/discretization/quadrature/backend/mfem/mfem_static_order.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "serif/discretization/blocks/concepts.hpp" +#include "serif/discretization/forms/operands.hpp" +#include "serif/utils/types/type_list.hpp" +#include "serif/discretization/quadrature/backend/order.hpp" + +namespace serif::discretization::quadrature::backend::mfem { + template + struct MFEMOperandListOrder; + + template + struct MFEMOperandListOrder> final { + static constexpr int value = (resolve_operand_order() + ... + 0); + }; + + template + inline constexpr int mfem_form_static_order = MFEMOperandListOrder::value; +} \ No newline at end of file diff --git a/src/include/serif/discretization/quadrature/backend/order.hpp b/src/include/serif/discretization/quadrature/backend/order.hpp new file mode 100644 index 0000000..a6ee099 --- /dev/null +++ b/src/include/serif/discretization/quadrature/backend/order.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include "serif/discretization/functional_space/space.hpp" +#include "serif/discretization/blocks/concepts.hpp" +#include "serif/discretization/forms/operands.hpp" +#include "serif/utils/error/errors.hpp" + +namespace serif::discretization::quadrature::backend::mfem { + template + struct DiscreteFunctionalSpaceTraits; + + template + struct DiscreteFunctionalSpaceTraits> final { + static constexpr auto space = SpaceV; + static constexpr auto order = static_cast(OrderV); + static constexpr std::size_t rank = static_cast(RankV); + }; + + template + using FunctionalSpaceTraits = DiscreteFunctionalSpaceTraits; + + template + [[nodiscard]] consteval int resolve_operand_order() noexcept { + using Operand = std::remove_cvref_t; + using Term = Operand::term_type; + using Operation = Operand::operation_type; + + if constexpr (!blocks::HasFunctionalSpace) { + static_assert(std::same_as, "Non-Spatial terms only support the value operation at the moment."); + return 0; + } else { + using Space = FunctionalSpaceTraits; + + constexpr auto family = Space::space.family; + constexpr int order = Space::order; + + if constexpr (std::same_as) { + if constexpr (family == functional_space::Family::hdiv) { + return order + 1; + } else { + return order; + } + } else if constexpr (std::same_as) { + static_assert(family == functional_space::Family::h1, "Only H1 spaces support the gradient operation at the moment."); + + return order > 0 ? order - 1 : 0; + } else if constexpr (std::same_as) { + static_assert(family == functional_space::Family::hdiv, "Only H(div) spaces support the divergence operation at the moment."); + + return order; + } else if constexpr (std::same_as) { + static_assert(family == functional_space::Family::hcurl, "Only H(curl) spaces support the curl operation at the moment."); + + return order > 0 ? order - 1 : 0; + } else if constexpr (std::same_as) { + static_assert(family == functional_space::Family::hdiv, "Only H(div) spaces support the normal trace operation at the moment."); + + return order; + } else { + static_assert(false, "Unsupported operation type. This indicates that an operation was used which is not currently registered in the operand configuration system. If you are a developer and you are seeing this error, please register your operation in the resolve_operand_order function in discretization/quadrature/detail.hpp. If you are a user and you are seeing this error, please report it to the SERiF developers."); + return 0; + } + } + } + + [[nodiscard]] inline int sum_dynamic_orders(const std::span dynamic_orders) { + int total = 0; + for (const int order : dynamic_orders) { + if (order < 0) { + utils::errors::serif_error("Dynamic quadrature order cannot be negative. Received {}.", order); + } + + total += order; + } + return total; + } +} + diff --git a/src/include/serif/discretization/quadrature/policy.hpp b/src/include/serif/discretization/quadrature/policy.hpp new file mode 100644 index 0000000..fe63564 --- /dev/null +++ b/src/include/serif/discretization/quadrature/policy.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "serif/discretization/quadrature/query.hpp" +#include "serif/discretization/quadrature/rules.hpp" + +namespace serif::discretization::quadrature { + class Policy final { + public: + explicit Policy(RuleSet ruleSet); + + [[nodiscard]] Resolution resolve(const Query &query) const; + + private: + [[nodiscard]] static int compute_base_order(const Query &query); + [[nodiscard]] const RuleControl &get_role_control(QuadratureRole role) const; + + private: + RuleSet m_ruleSet; + }; +} \ No newline at end of file diff --git a/src/include/serif/discretization/quadrature/query.hpp b/src/include/serif/discretization/quadrature/query.hpp new file mode 100644 index 0000000..af16417 --- /dev/null +++ b/src/include/serif/discretization/quadrature/query.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include +#include + +#include "serif/discretization/quadrature/rules.hpp" +#include "serif/discretization/domain/physical_domains.hpp" +#include "serif/discretization/forms/form_traits.hpp" +#include "serif/discretization/forms/runtime.hpp" + +namespace serif::discretization::quadrature { + struct Query final { + forms::FormDescriptor form; + + QuadratureRole role = QuadratureRole::discretization; + domain::DomainOrSet domain = domain::AllDomains{}; + MappingKind mapping = MappingKind::none; + + int trial_order = 0; + int test_order = 0; + int coefficient_order = 0; + int geometry_weight_order = 0; + int trial_order_reduction = 0; + int test_order_reduction = 0; + + std::optional base_order; + }; + + template + [[nodiscard]] constexpr Query make_query( + const QuadratureRole role, + const domain::DomainOrSet domain, + const MappingKind mapping, + const int trialOrder, + const int testOrder, + const int coefficientOrder, + const int geometryWeightOrder, + const std::optional baseOrder = std::nullopt + ) noexcept { + return Query{ + .form = forms::form_descriptor, + .role = role, + .domain = domain, + .mapping = mapping, + .trial_order = trialOrder, + .test_order = testOrder, + .coefficient_order = coefficientOrder, + .geometry_weight_order = geometryWeightOrder, + .trial_order_reduction = forms::FormTraits::trial_order_reduction, + .test_order_reduction = forms::FormTraits::test_order_reduction, + .base_order = baseOrder + }; + } +} + +template <> +struct std::formatter : std::formatter { + template + auto format(const serif::discretization::quadrature::Query& q, FormatContext& ctx) const { + return std::formatter::format(std::format("Query(form={}, role={}, domain={}, mapping={}, trial_order={}, test_order={}, coefficient_order={}, geometry_weight_order={}, trial_order_reduction={}, test_order_reduction={}, base_order={})", + q.form, + q.role, + q.domain, + q.mapping, + q.trial_order, + q.test_order, + q.coefficient_order, + q.geometry_weight_order, + q.trial_order_reduction, + q.test_order_reduction, + (q.base_order.has_value() ? std::to_string(q.base_order.value()) : "nullopt") + ), ctx); + } +}; diff --git a/src/include/serif/discretization/quadrature/rules.hpp b/src/include/serif/discretization/quadrature/rules.hpp new file mode 100644 index 0000000..ed66a10 --- /dev/null +++ b/src/include/serif/discretization/quadrature/rules.hpp @@ -0,0 +1,342 @@ +#pragma once +#include +#include +#include +#include +#include + +#include "serif/discretization/forms/runtime.hpp" + +namespace serif::discretization::quadrature { + enum class QuadratureRole { + discretization, + preconditioner, + diagnostic, + projection + }; + + enum class MappingKind { + none, + affine, + general, + kelvin + }; + + enum class Mode { + fast, + production, + reference, + convergence + }; + + struct RuleControl { + std::optional fixed_order; + int boost = 0; + }; + + struct FormRule final { + forms::FormDescriptor form; + RuleControl control; + }; + + class RuleSet { + public: + template + void set_form_control( + const RuleControl control + ) { + set_form_control(forms::form_descriptor, control); + } + + void set_form_control( + const forms::FormDescriptor &form, + const RuleControl control + ) { + for (auto &[formDescriptor, ruleControl] : m_formRules) { + if (formDescriptor == form) { + ruleControl = control; + return; + } + } + + m_formRules.push_back(FormRule{.form = form, .control = control}); + } + + [[nodiscard]] const RuleControl *find_form_control( + const forms::FormDescriptor &form + ) const noexcept { + for (const auto &[formDescriptor, ruleControl] : m_formRules) { + if (formDescriptor == form) { + return &ruleControl; + } + } + + return nullptr; + } + + void set_role_control( + const QuadratureRole role, + const RuleControl control + ) noexcept { + switch (role) { + case QuadratureRole::discretization: + m_discretizationControl = control; + break; + + case QuadratureRole::preconditioner: + m_preconditionerControl = control; + break; + + case QuadratureRole::diagnostic: + m_diagnosticControl = control; + break; + + case QuadratureRole::projection: + m_projectionControl = control; + break; + } + } + + [[nodiscard]] const RuleControl &role_control( + const QuadratureRole role + ) const noexcept { + switch (role) { + case QuadratureRole::discretization: + return m_discretizationControl; + + case QuadratureRole::preconditioner: + return m_preconditionerControl; + + case QuadratureRole::diagnostic: + return m_diagnosticControl; + + case QuadratureRole::projection: + return m_projectionControl; + } + + std::unreachable(); + } + + void set_fallback_control( + const RuleControl control + ) noexcept { + m_fallbackControl = control; + } + + [[nodiscard]] const RuleControl &fallback_control() const noexcept { + return m_fallbackControl; + } + + auto begin() { + return m_formRules.begin(); + } + + auto end() { + return m_formRules.end(); + } + + auto begin() const { + return m_formRules.begin(); + } + + auto end() const { + return m_formRules.end(); + } + + private: + std::vector m_formRules; + + RuleControl m_discretizationControl; + RuleControl m_preconditionerControl; + RuleControl m_diagnosticControl; + RuleControl m_projectionControl; + + RuleControl m_fallbackControl; + }; + + struct RoleControls { + RuleControl discretization; + RuleControl preconditioner; + RuleControl diagnostic; + RuleControl projection; + }; + + + struct Resolution { + int base_order; + int boost; + int order; + bool used_fixed_order; + }; + + struct QuadratureTermOptions { + std::optional fixed_order; + int additional_boost = 0; + }; + + struct QuadratureManifestOptions { + bool enabled = false; + bool include_repeated_queries = false; + std::optional output_file; + }; + + struct QuadratureValidationOptions { + bool require_explicit_base_order = false; + bool require_explicit_mfem_rule = false; + bool reject_negative_boosts = true; + bool report_unused_overrides = true; + }; + + struct QuadratureRoleOptions { + QuadratureTermOptions discretization; + QuadratureTermOptions preconditioner; + QuadratureTermOptions diagnostic; + QuadratureTermOptions projection; + }; + + struct QuadratureOptions { + Mode mode = Mode::production; + int global_boost = 0; + std::optional fallback_fixed_order; + + QuadratureTermOptions gravity_hdiv_mass; + QuadratureTermOptions gravity_divergence; + QuadratureTermOptions gravity_source; + QuadratureTermOptions gravity_force; + QuadratureTermOptions gravity_boundary; + QuadratureTermOptions centrifugal; + QuadratureTermOptions density_projection; + QuadratureTermOptions eos_closure; + QuadratureTermOptions hydrostatic_equilibrium; + QuadratureTermOptions isobaric_surface; + QuadratureTermOptions mesh_extension; + QuadratureTermOptions mass_conservation; + QuadratureTermOptions mass_normalization; + QuadratureTermOptions center_of_mass; + QuadratureTermOptions quadrupole; + QuadratureTermOptions gravitational_energy; + QuadratureTermOptions pressure_integral; + QuadratureTermOptions pressure_force; + QuadratureTermOptions virial; + QuadratureTermOptions error_norm; + + QuadratureRoleOptions roles; + + std::vector convergence_boosts = {0, 2, 4}; + QuadratureManifestOptions manifest; + QuadratureValidationOptions validation; + }; + +} + +template <> +struct std::formatter : std::formatter { + template + auto format(const serif::discretization::quadrature::QuadratureRole& role, FormatContext& ctx) const { + std::string_view roleStr; + switch (role) { + case serif::discretization::quadrature::QuadratureRole::preconditioner: + roleStr = "preconditioner"; + break; + case serif::discretization::quadrature::QuadratureRole::diagnostic: + roleStr = "diagnostic"; + break; + case serif::discretization::quadrature::QuadratureRole::projection: + roleStr = "projection"; + break; + default: + roleStr = "unknown"; + break; + } + return std::formatter::format(roleStr, ctx); + } +}; + +template <> +struct std::formatter : std::formatter { + template + auto format(const serif::discretization::quadrature::MappingKind& mk, FormatContext& ctx) const { + std::string_view mappingKindStr; + switch (mk) { + case serif::discretization::quadrature::MappingKind::none: + mappingKindStr = "none"; + break; + case serif::discretization::quadrature::MappingKind::affine: + mappingKindStr = "affine"; + break; + case serif::discretization::quadrature::MappingKind::general: + mappingKindStr = "general"; + break; + case serif::discretization::quadrature::MappingKind::kelvin: + mappingKindStr = "kelvin"; + break; + default: + mappingKindStr = "unknown"; + break; + } + return std::formatter::format(mappingKindStr, ctx); + } +}; + +template <> +struct std::formatter : std::formatter { + template + auto format(const serif::discretization::quadrature::Mode& mode, FormatContext& ctx) const { + std::string_view modeStr; + switch (mode) { + case serif::discretization::quadrature::Mode::fast: + modeStr = "fast"; + break; + case serif::discretization::quadrature::Mode::production: + modeStr = "production"; + break; + case serif::discretization::quadrature::Mode::reference: + modeStr = "reference"; + break; + case serif::discretization::quadrature::Mode::convergence: + modeStr = "convergence"; + break; + default: + modeStr = "unknown"; + break; + } + return std::formatter::format(modeStr, ctx); + } +}; + +template <> +struct std::formatter : std::formatter { + template + auto format(const serif::discretization::quadrature::RuleControl& rc, FormatContext& ctx) const { + return std::formatter::format( + std::format("RuleControl(fixed_order={}, boost={})", rc.fixed_order.has_value() ? std::to_string(rc.fixed_order.value()) : "none", rc.boost), + ctx); + } +}; + +template <> +struct std::formatter : std::formatter { + template + auto format(const serif::discretization::quadrature::FormRule& fr, FormatContext& ctx) const { + return std::formatter::format( + std::format("FormRule(form={}, control={})", fr.form, fr.control), + ctx); + } +}; + +template <> +struct std::formatter : std::formatter { + template + auto format(const serif::discretization::quadrature::RuleSet& rs, FormatContext& ctx) const { + std::string rulesStr; + for (const auto& rule : rs) { + if (!rulesStr.empty()) { + rulesStr += ", "; + } + rulesStr += std::format("{}", rule); + } + return std::formatter::format( + std::format("RuleSet(formRules=[{}])", rulesStr), + ctx); + } +}; \ No newline at end of file diff --git a/src/include/serif/eos/exceptions.hpp b/src/include/serif/eos/exceptions.hpp index 17ba97b..460f897 100644 --- a/src/include/serif/eos/exceptions.hpp +++ b/src/include/serif/eos/exceptions.hpp @@ -4,29 +4,9 @@ #include namespace serif::eos { - enum class EOSEvaluationErrorCode : uint8_t { - unsupported_relation, - unsupported_derivative, - wrong_input_count, - wrong_input_quantity, - nonfinite_input, - outside_domain, - nonfinite_result, - invalid_thermodynamic_input - }; - class EOSEvaluationError final : public std::domain_error { public: - explicit EOSEvaluationError( - const EOSEvaluationErrorCode code, - std::string message ): - std::domain_error(std::move(message)), - m_code(code) {} - - [[nodiscard]] EOSEvaluationErrorCode code() const noexcept { - return m_code; - } - private: - EOSEvaluationErrorCode m_code; + explicit EOSEvaluationError(std::string message): + std::domain_error(std::move(message)){} }; } diff --git a/src/include/serif/eos/models/polytropic.hpp b/src/include/serif/eos/models/polytropic.hpp index 6fc2466..3c88f26 100644 --- a/src/include/serif/eos/models/polytropic.hpp +++ b/src/include/serif/eos/models/polytropic.hpp @@ -1,9 +1,7 @@ #pragma once -#include #include "serif/eos/relations/relations.hpp" #include "serif/dimensions/type_alias.hpp" #include "serif/dimensions/partial.hpp" -#include "serif/utils/misc/finite.hpp" namespace serif::eos::models { struct PolytropeParameters final { diff --git a/src/include/serif/eos/runtime/details.hpp b/src/include/serif/eos/runtime/details.hpp index 5875f0e..4af28d0 100644 --- a/src/include/serif/eos/runtime/details.hpp +++ b/src/include/serif/eos/runtime/details.hpp @@ -7,6 +7,7 @@ #include "serif/dimensions/runtime/concepts.hpp" #include "serif/eos/exceptions.hpp" #include "serif/eos/relations/catalog.hpp" +#include "serif/utils/error/codes.hpp" namespace serif::eos::runtime { template @@ -134,32 +135,31 @@ namespace serif::eos::runtime { }; }; - [[nodiscard]] inline std::expected runtimeEvaluationFailure( - const EOSEvaluationErrorCode code, - std::string message + [[nodiscard]] inline std::expected runtimeEvaluationFailure( + const utils::errors::SERiFErrorCode code ) { - return std::unexpected{EOSEvaluationError{code, std::move(message)}}; + return std::unexpected{code}; } template - [[nodiscard]] std::expected evaluateRuntimeRelation( + [[nodiscard]] std::expected evaluateRuntimeRelation( const EOS& eos, relations::Relation /*relation*/, const std::span inputValues ) { - /* Once again we find ourself at a use of folding. I reccomend you read the comment earlier in this file regarding folding, or find the C++ docs on folding. + /* Once again we find ourself at a use of folding. I recommend you read the comment earlier in this file regarding folding, or find the C++ docs on folding. * - * The general premis here is that we call the evaluation function for the EOS with the given relation and input values. We fold over all indicies using the final ... operator. + * The general premise here is that we call the evaluation function for the EOS with the given relation and input values. We fold over all indices using the final ... operator. * This is an implicit loop generated by the compiler over the so called "parameter pack". */ const auto invoke_evaluate = [&](std::index_sequence) { - return eos::evaluate(eos, dimensions::QuantityValue{inputValues[Indices].value}...); + return eos::evaluate(eos, dimensions::QuantityValue{inputValues[Indices].value}...).value(); }; try { return invoke_evaluate(std::make_index_sequence{}); } catch (const EOSEvaluationError& e) { // We may want to reconsider using a try-catch for this, but this is good enough for now. Realistically if we want to not use a try catch we will need to change the signature of the EOS evaluation function to return a std::expected instead of throwing an exception. This is a larger change that I don't want to make right now. - return std::unexpected{e}; + return std::unexpected{utils::errors::SERiFErrorCode::EOS_ERROR}; } } @@ -169,7 +169,7 @@ namespace serif::eos::runtime { relations::Relation /*relation*/, const dimensions::runtime::ThermodynamicQuantityID withRespectTo, const std::span inputValues, - std::expected& result + std::expected& result ) { if (withRespectTo != dimensions::runtime::thermodynamicQuantityID) { return false; @@ -182,26 +182,25 @@ namespace serif::eos::runtime { try { result = invoke_evaluate_partial_derivative(std::index_sequence_for{}); - } catch (const EOSEvaluationError& e) { - result = std::unexpected{e}; + } catch (const EOSEvaluationError& /*e*/) { + result = std::unexpected{utils::errors::SERiFErrorCode::EOS_UNSUPPORTED_DERIVATIVE}; } } else { - result = runtimeEvaluationFailure(EOSEvaluationErrorCode::unsupported_derivative, "The requested EOS partial derivative is not available in the selected EOS."); + result = runtimeEvaluationFailure(utils::errors::SERiFErrorCode::EOS_UNSUPPORTED_DERIVATIVE); } return true; } template - [[nodiscard]] std::expected evaluateRuntimePartialDerivative ( + [[nodiscard]] std::expected evaluateRuntimePartialDerivative ( const EOS& eos, relations::Relation relation, const dimensions::runtime::ThermodynamicQuantityID withRespectTo, const std::span inputValues ) { - std::expected result = runtimeEvaluationFailure( - EOSEvaluationErrorCode::unsupported_derivative, - "The requested quantity is not an input to the EOS relation." + std::expected result = runtimeEvaluationFailure( + utils::errors::SERiFErrorCode::EOS_UNSUPPORTED_DERIVATIVE ); [[maybe_unused]] const bool matched = (tryRuntimePartialDerivative(eos, relation, withRespectTo, inputValues, result) || ...); // Here we fold over all input and try to find one that we can evaluate. @@ -233,7 +232,7 @@ namespace serif::eos::runtime { template struct RuntimeCatalogDispatch> { - [[nodiscard]] static std::expected evaluate( + [[nodiscard]] static std::expected evaluate( const void *object, const dimensions::runtime::ThermodynamicQuantityID& outputQuantity, const std::span inputValues @@ -241,8 +240,8 @@ namespace serif::eos::runtime { const auto &eos = *static_cast(object); // Not sure if polymorphic type erasure is the right tool here. It works but we may want to revisit it if it precent compiler optimizations. This does however let us pass any eos model to the runtime view without needing to know the type at compile time (which is therefore helpful when building extension systems in other languages like python). Further, it may make sense to take the performance hit here and use something like a dynamic_cast to ensure that the object is actually of the correct type. This would be a runtime check but it would be a more robust check than just blindly casting to the expected type. - std::expected result = runtimeEvaluationFailure( - EOSEvaluationErrorCode::unsupported_relation, "The requested EOS relation is not available in the current equation of state." + std::expected result = runtimeEvaluationFailure( + utils::errors::SERiFErrorCode::EOS_UNSUPPORTED_RELATION ); /* Fold over all relations in the catalog and check if any of them match the requested output quantity and input quantities. If the match is found then @@ -253,7 +252,7 @@ namespace serif::eos::runtime { return result; } - [[nodiscard]] static std::expected partial_derivative( + [[nodiscard]] static std::expected partial_derivative( const void *object, const dimensions::runtime::ThermodynamicQuantityID& outputQuantity, const dimensions::runtime::ThermodynamicQuantityID& withRespectTo, @@ -261,8 +260,8 @@ namespace serif::eos::runtime { ) { const auto &eos = *static_cast(object); // Same Comment as above - std::expected result = runtimeEvaluationFailure( - EOSEvaluationErrorCode::unsupported_relation, "The requested EOS relation is not available in the current equation of state." + std::expected result = runtimeEvaluationFailure( + utils::errors::SERiFErrorCode::EOS_UNSUPPORTED_RELATION ); [[maybe_unused]] const bool matched = ((runtimeRelationMatches(outputQuantity, inputValues) ? (result = evaluateRuntimePartialDerivative(eos, RelationTs{}, withRespectTo, inputValues), true) : false) || ...); diff --git a/src/include/serif/eos/runtime/views.hpp b/src/include/serif/eos/runtime/views.hpp index ff88dbd..6e42021 100644 --- a/src/include/serif/eos/runtime/views.hpp +++ b/src/include/serif/eos/runtime/views.hpp @@ -7,11 +7,13 @@ namespace serif::eos::runtime { class EOSView final { public: - template - explicit EOSView(EOS& eos) noexcept : + template + requires RuntimeEOSModel> + explicit EOSView(const EOS& eos) noexcept : m_object(std::addressof(eos)), - m_relations(&RuntimeEOSAdapter>::evaluate), - m_partialDerivative(&RuntimeEOSAdapter>::partial_derivative) {} + m_relations(runtimeRelationDescriptors>()), + m_evaluate(&RuntimeEOSAdapter>::evaluate), + m_partialDerivative(&RuntimeEOSAdapter>::partial_derivative) {} // TODO: This function can be moved to an implantation file, it may need to be to prevent ODR violations [[nodiscard]] std::span relations() const noexcept { @@ -26,36 +28,113 @@ namespace serif::eos::runtime { return find_relation(outputQuantity, inputQuantities) != nullptr; } - [[nodiscard]] std::expected try_evaluate( + [[nodiscard]] std::expected try_evaluate( const dimensions::runtime::ThermodynamicQuantityID outputQuantity, const std::span inputQuantities - ) { + ) const { const auto validation = validate_relation_request(outputQuantity, inputQuantities); if (!validation.has_value()) { - return std::unexpected{validation.error()}; + return std::unexpected{validation.error()}; } auto result = m_evaluate(m_object, outputQuantity, inputQuantities); if (!result.has_value()) { - return std::unexpected{result.error()}; + return std::unexpected{result.error()}; } return dimensions::runtime::RuntimeQuantityValue{outputQuantity, *result}; } - // TODO: Still need to translate from the old code the templated version of try_evaluate along with the try_partial_derivative function. The templated version is more user friendly and should be kept, but the non-templated version is needed for the runtime view. + template + [[nodiscard]] std::expected, utils::errors::SERiFErrorCode> try_evaluate( + const InputValues... inputValues + ) const { + constexpr bool inputsHaveRuntimeIdentifiers = (dimensions::runtime::RuntimeIdentifiedThermodynamicQuantity> && ...); + + static_assert(inputsHaveRuntimeIdentifiers, "Every runtime EOS input quantity needs a stable identifier."); + + const std::array runtimeInputs { + dimensions::runtime::RuntimeQuantityValue{ + .id = dimensions::runtime::thermodynamicQuantityID>, .value = inputValues.value() + }... + }; + + auto result = try_evaluate( + dimensions::runtime::thermodynamicQuantityID, std::span{runtimeInputs} + ); + + if (!result.has_value()) { + return std::unexpected{result.error()}; + } + + return dimensions::QuantityValue{result->value}; + } + + [[nodiscard]] std::expected try_partial_derivative( + const dimensions::runtime::ThermodynamicQuantityID outputQuantity, + const dimensions::runtime::ThermodynamicQuantityID withRespectTo, + const std::span inputValues + ) const { + + const auto validation = validate_relation_request(outputQuantity, inputValues); + + if (!validation.has_value()) { + return std::unexpected{validation.error()}; + } + + const dimensions::runtime::RuntimeRelationDescriptor &descriptor = **validation; + bool derivativeAvailable = false; + + for (std::size_t index = 0; index < descriptor.inputQuantities.size(); ++index) { + if (descriptor.inputQuantities[index] == withRespectTo) { + derivativeAvailable = descriptor.hasPartialDerivative(index); + break; + } + } + + if (!derivativeAvailable) { + return runtime_failure(utils::errors::SERiFErrorCode::EOS_UNSUPPORTED_DERIVATIVE); + } + + return m_partialDerivative(m_object, outputQuantity, withRespectTo, inputValues); + } + + template + [[nodiscard]] std::expected, EOSEvaluationError> try_partial_derivative( + const InputValues... inputValues + ) const { + constexpr bool inputHaveRuntimeIdentifiers = (dimensions::runtime::RuntimeIdentifiedThermodynamicQuantity> && ...); + static_assert(inputHaveRuntimeIdentifiers, "Every runtime EOS input quantity needs a stable identifier."); + + const std::array runtimeInputs{ + dimensions::runtime::RuntimeQuantityValue{.id = dimensions::runtime::thermodynamicQuantityID>, .value = inputValues.value()}... + }; + + auto result = try_partial_derivative( + dimensions::runtime::thermodynamicQuantityID, // output + dimensions::runtime::thermodynamicQuantityID, // input + std::span{runtimeInputs} // where to evaluate + ); + + if (!result.has_value()) { + return std::unexpected{result.error()}; + } + + return dimensions::PartialDerivative{*result}; + } + private: // Type aliases - using RuntimeEvaluateFunction = std::expected (*)( + using RuntimeEvaluateFunction = std::expected (*)( const void*, - dimensions::runtime::ThermodynamicQuantityID, + const dimensions::runtime::ThermodynamicQuantityID&, std::span ); - using RuntimePartialDerivativeFunction = std::expected (*)( + using RuntimePartialDerivativeFunction = std::expected (*)( const void*, - dimensions::runtime::ThermodynamicQuantityID, - dimensions::runtime::ThermodynamicQuantityID, + const dimensions::runtime::ThermodynamicQuantityID&, + const dimensions::runtime::ThermodynamicQuantityID&, std::span ); private: // Private methods @@ -84,7 +163,7 @@ namespace serif::eos::runtime { return nullptr; } - [[nodiscard]] std::expected validate_relation_request( + [[nodiscard]] std::expected validate_relation_request( const dimensions::runtime::ThermodynamicQuantityID outputQuantity, const std::span inputValues ) const { @@ -114,32 +193,26 @@ namespace serif::eos::runtime { if (!outputAvailable) { return runtime_failure( - EOSEvaluationErrorCode::unsupported_relation, - "The requested EOS relation is not available." + utils::errors::SERiFErrorCode::EOS_UNSUPPORTED_RELATION ); } if (!inputCountAvailable) { return runtime_failure( - EOSEvaluationErrorCode::wrong_input_count, - "No EOS relation for given output quantity '" + std::string{outputQuantity.name()} + "' accepts the provided number of inputs." + utils::errors::SERiFErrorCode::EOS_WRONG_INPUT_COUNT ); } return runtime_failure( - EOSEvaluationErrorCode::wrong_input_quantity, - "No EOS relation for given output quantity '" + std::string{outputQuantity.name()} + "' accepts the provided input quantities." + utils::errors::SERiFErrorCode::EOS_WRONG_INPUT_QUANTITY ); } template - [[nodiscard]] static std::expected runtime_failure( - const EOSEvaluationErrorCode code, - const std::string message + [[nodiscard]] static std::expected runtime_failure( + const utils::errors::SERiFErrorCode code ) { - return std::unexpected{ - EOSEvaluationError{code, std::move(message)} - }; + return std::unexpected{code}; } private: // Private members diff --git a/src/include/serif/meta/enums.hpp b/src/include/serif/meta/enums.hpp new file mode 100644 index 0000000..6d47014 --- /dev/null +++ b/src/include/serif/meta/enums.hpp @@ -0,0 +1,31 @@ +#pragma once +#include +#include + +#include "magic_enum/magic_enum_all.hpp" + +namespace serif::meta { + template + [[nodiscard]] constexpr std::unordered_map enum_to_string_enum_map() noexcept { + static_assert(std::is_enum_v, "EnumType must be an enum type"); + + std::unordered_map map; + + for (const auto& [value, name] : magic_enum::enum_entries()) { + map.emplace(name, value); + } + return map; + } + + template + [[nodiscard]] constexpr std::unordered_map enum_to_enum_string_map() noexcept { + static_assert(std::is_enum_v, "EnumType must be an enum type"); + + std::unordered_map map; + + for (const auto& [value, name] : magic_enum::enum_entries()) { + map.emplace(value, name); + } + return map; + } +} \ No newline at end of file diff --git a/src/include/serif/meta/names.hpp b/src/include/serif/meta/names.hpp new file mode 100644 index 0000000..0ac9ef3 --- /dev/null +++ b/src/include/serif/meta/names.hpp @@ -0,0 +1,25 @@ +#pragma once +#include + +#include "nameof.hpp" + +/* If we ever move to C++26 or above we can replace these with native reflection + */ +namespace serif::meta { + template + [[nodiscard]] consteval std::string_view type_name() noexcept { + return nameof::nameof_short_type(); + } + + template + [[nodiscard]] consteval std::string_view template_name() noexcept { + constexpr std::string_view typeName = type_name(); + constexpr std::size_t templateStart = typeName.find('<'); + + if constexpr (templateStart == std::string_view::npos) { + return typeName; + } + + return typeName.substr(0, templateStart); + } +} \ No newline at end of file diff --git a/src/include/serif/utils/error/codes.hpp b/src/include/serif/utils/error/codes.hpp new file mode 100644 index 0000000..510a954 --- /dev/null +++ b/src/include/serif/utils/error/codes.hpp @@ -0,0 +1,52 @@ +#pragma once +#include + +#include "serif/meta/enums.hpp" + +namespace serif::utils::errors { + // Each section should start at another hundred to allow for simple reading of error codes by users + // C++ enums automatically count up from the previous value so you only need to specify the first + // member of that category. By convention please make the first member the most general + // class of error in that category, subsequent errors can specify further. + // subcategories should follow the same patter but in 10s + + // Further, by convention please keep all names upper case. The meta functions + // will convert all lower case names to upper case but its good convention to + // stay consistent here. + + // At some point someone should go through and come up with some well defined schema for all this + enum class SERiFErrorCode : uint16_t { + UNKNOWN_ERROR = 0, + + // RUNTIME FAILURES (>100) + // Invalid argument errors + INVALID_ARGUMENT = 100, + INVALID_QUADRATURE_ORDER, + + INVALID_DOMAIN_ERROR=110, + NEGATIVE_VALUE_ERROR, + NON_FINITE_VALUE_ERROR, + + // Jacobian Errors + JACOBIAN_ERROR = 200, + INVERTED_JACOBIAN, + + // EOS Errors + EOS_ERROR = 300, + EOS_UNSUPPORTED_RELATION, + EOS_UNSUPPORTED_DERIVATIVE, + EOS_WRONG_INPUT_COUNT, + EOS_WRONG_INPUT_QUANTITY, + EOS_NONFINITE_INPUT, + EOS_OUTSIDE_DOMAIN, + EOS_NONFINITE_RESULT, + EOS_INVALID_THERMODYNAMIC_INPUT, + + // COMPILE TIME FAILURES (>1000) + // Type Invariant Failures + FAILED_TYPE_INVARIANT = 1000 + }; + + inline auto SERiFErrorCodeNameToTypeMap = meta::enum_to_string_enum_map(); + inline auto SERiFErrorCodeTypeToNameMap = meta::enum_to_enum_string_map(); +} \ No newline at end of file diff --git a/src/include/serif/utils/error/errors.hpp b/src/include/serif/utils/error/errors.hpp new file mode 100644 index 0000000..91165b3 --- /dev/null +++ b/src/include/serif/utils/error/errors.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "serif/utils/misc/terminal/colors.hpp" +#include "serif/utils/misc/concepts/exceptions.hpp" +#include "serif/utils/error/codes.hpp" +#include "serif/utils/types/strings.hpp" + +// The *ONLY* place in SERiF that throw should +// be called is here. All other error throwing locations should +// call serif_error to ensure we have consistent handling of errors + +// If you want to throw a warning then look at the parallel warnings module and call serif_warning +// Note errors cannot be disabled at compile time, warnings can be disabled to a noop at compile time. + +namespace serif::utils::errors { + template + void serif_error(const std::string_view message, auto... args) { + std::string error_msg; + std::string code_name{SERiFErrorCodeTypeToNameMap.at(code)}; + + std::ranges::transform(code_name, code_name.begin(), [](const unsigned char c){ return std::toupper(c); }); + if (misc::terminal::supports_ansi(stderr)) { + error_msg = std::format("{}{}{}: {}", misc::terminal::a_red, "Error", misc::terminal::a_reset, std::vformat(message, std::make_format_args(args...))); + // format error code with color if terminal supports ANSI + error_msg = std::format("{} (code No.: {} [{}{}{}])", error_msg, static_cast>(code), misc::terminal::a_red, code_name, misc::terminal::a_reset); + } else { + error_msg = std::format("Error: {}", std::vformat(message, std::make_format_args(args...))); + // Add error code without color if terminal does not support ANSI + error_msg = std::format("{} (code No.: {} [{}])", error_msg, static_cast>(code), code_name); + } + + throw ExceptionT(error_msg); + } + + template + void serif_assert(const bool condition, const std::string_view message, auto... args) { + // collapse to a noop if NDEBUG is defined +#ifndef NDEBUG + constexpr bool is_debug_build = true; +#else + constexpr bool is_debug_build = false; +#endif + if constexpr(is_debug_build) { + if (!condition) { + serif_error(message, args...); + } + } + } + + + template + struct serif_error_printer; + + template + constexpr void serif_assert() { +#ifndef NDEBUG + constexpr bool is_debug_build = true; +#else + constexpr bool is_debug_build = false; +#endif + + if constexpr (is_debug_build) { + if constexpr (!condition) { + // Trigger an intentional error to display the message and code at compile time + + // Note if we move to C++26 we can replace this with a more standard and less convoluted + // formated static_assert. + sizeof(serif_error_printer); + } + } + } +} \ No newline at end of file diff --git a/src/include/serif/utils/misc/concepts/concat.hpp b/src/include/serif/utils/misc/concepts/concat.hpp new file mode 100644 index 0000000..d51fe92 --- /dev/null +++ b/src/include/serif/utils/misc/concepts/concat.hpp @@ -0,0 +1,25 @@ +#pragma once +#include "serif/utils/types/type_list.hpp" + +namespace serif::utils::misc::concepts { + template + struct Concat; + + template <> + struct Concat<> { + using Type = types::TypeList<>; + }; + + template + struct Concat> { + using Type = types::TypeList; + }; + + template + struct Concat, types::TypeList, Rest...> { + using Type = typename Concat, Rest...>::Type; + }; + + template + using ConcatT = Concat::Type; +} \ No newline at end of file diff --git a/src/include/serif/utils/misc/concepts/enumeration.hpp b/src/include/serif/utils/misc/concepts/enumeration.hpp new file mode 100644 index 0000000..ea76794 --- /dev/null +++ b/src/include/serif/utils/misc/concepts/enumeration.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "serif/utils/misc/concepts/concat.hpp" +#include "serif/utils/types/type_list.hpp" + +namespace serif::utils::misc::concepts { + // Might make sense to rename these to FlatMap or some such + template typename Descriptor> + struct EnumerateTypes; + + template