Skip to content

Add enchantum as an enum reflection backend #441

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 29, 2025

Conversation

ZXShady
Copy link
Contributor

@ZXShady ZXShady commented May 28, 2025

Disclaimer I am the author of enchantum.

This PR replaces reflect-cpp own enum reflection with enchantum's

  1. It reduces compile times
  2. It reduces the need to maintain this backend
  3. it allows for more customization you can for example choose to remove a prefix on all enums by using enchantum::enum_traits
  4. it allows reflection of all enums types
    (except unnamed unscoped enums) and not just scoped enums.
enum {
  Hello // Unreflectable the main enum is unnamed
};

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @ZXShady, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

Summary of Changes

As gemini-code-assist, I've reviewed this pull request from ZXShady. The core intent of this PR is to replace the existing internal enum reflection implementation within reflect-cpp with an external library called enchantum. The author, who is also the author of enchantum, states that this change aims to reduce compile times, decrease maintenance burden on reflect-cpp for enum reflection, and provide more customization options through enchantum::enum_traits (like removing prefixes). The changes involve integrating the enchantum header and refactoring the internal enum handling logic to delegate to the new library.

Highlights

  • Enum Reflection Backend Replacement: The primary change is the complete replacement of reflect-cpp's custom enum reflection code with the enchantum library.
  • Integration of enchantum: The enchantum library is added as a third-party header file and integrated into the internal enum handling components.
  • Simplified Internal Logic: The internal code for getting enum names, converting between enums and strings, and determining enum properties (like being a flag enum) is significantly simplified by leveraging enchantum's functionalities.
  • Enum Range Configuration: The existing RFL_ENUM_RANGE_MIN and RFL_ENUM_RANGE_MAX macros are now used to configure the corresponding range macros within the enchantum library.

Changelog

Click here to see the changelog
  • include/rfl/internal/enums/Names.hpp
    • Moved and redefined RFL_ENUM_RANGE_MIN and RFL_ENUM_RANGE_MAX macros (lines 4-18).
    • Mapped RFL_ENUM_RANGE_MIN and RFL_ENUM_RANGE_MAX to ENCHANTUM_MIN_RANGE and ENCHANTUM_MAX_RANGE (lines 20-28).
    • Removed old definitions of RFL_ENUM_RANGE_MIN and RFL_ENUM_RANGE_MAX (lines 44-45).
    • Changed the type of enums_ member to auto for potential type deduction improvements (line 87).
  • include/rfl/internal/enums/StringConverter.hpp
    • Included the new enchantum.hpp header (line 13).
    • Replaced the manual loop for enum_to_single_string with a call to enchantum::to_string (lines 73-76).
    • Replaced the custom string-to-enum conversion logic in single_string_to_enum with a call to enchantum::cast (lines 87-95).
  • include/rfl/internal/enums/get_enum_names.hpp
    • Included the new enchantum.hpp header (line 13).
    • Removed the old compiler-specific get_enum_name_str_view and get_enum_name functions (lines 44-79).
    • Removed the complex NamesCombiner and CombineNames machinery (lines 108-160, 162-181).
    • Refactored get_enum_names to use enchantum::entries and convert the result to the internal Names structure (lines 74-109).
    • Removed include for is_scoped_enum.hpp (line 16).
  • include/rfl/internal/enums/is_flag_enum.hpp
    • Included the new enchantum.hpp header (line 7).
    • Added a template specialization for enchantum::is_bitflag based on the presence of bitwise operators (lines 8-12).
    • Updated the is_flag_enum concept to use enchantum::BitFlagEnum (lines 18-20).
  • include/rfl/internal/enums/is_scoped_enum.hpp
    • Included the new enchantum.hpp header (line 6).
    • Updated the is_scoped_enum concept to use enchantum::ScopedEnum (line 13).
  • include/rfl/thirdparty/enchantum.hpp
    • Added the complete enchantum library header file (lines 1-1495).
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.


Enums stand so tall,
Names reflected, answering the call.
Code shrinks, compile times fall.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces enchantum as a new backend for enum reflection, which is a significant change. The goals of reducing compile times, decreasing maintenance burden on rfl's enum reflection, and enabling more customization are well-aligned with improving the library. The integration of enchantum, especially the way rfl communicates its flag enum definition via enchantum::is_bitflag specialization, appears thoughtfully done.

However, there are a few points to address, primarily concerning a potentially breaking change in default enum ranges and associated documentation updates. Additionally, some code cleanup in the integration and a minor point in the vendored enchantum.hpp are suggested.

Please also note that docs/enums.md (line 44) will need an update to reflect the new default enum range [-256, 256] instead of [0, 127]. Since this file is not part of the PR, this is just a reminder for a follow-up or inclusion in this PR.

Overall, this is a promising direction for enum reflection in rfl.

Summary of Findings

  • Default Enum Range Change: The default values for RFL_ENUM_RANGE_MIN and RFL_ENUM_RANGE_MAX have been changed. This is a potentially breaking change and the accompanying comments are outdated.
  • Code Cleanup: Commented-out code from a previous implementation exists in get_enum_names.hpp and could be removed.
  • Macro Definition in enchantum.hpp: The ENCHANTUM_ASSERT macro in the vendored enchantum.hpp declares variadic arguments that are not used in its expansion.
  • Documentation Update Needed (Outside PR Scope): The main docs/enums.md file needs to be updated to reflect the new default enum ranges. (Noted as not commented due to settings, but mentioned in general feedback).

Merge Readiness

This pull request makes a significant and largely positive change by integrating enchantum for enum reflection. However, due to the potentially breaking change in default enum ranges and the outdated documentation for these macros, I recommend addressing these points before merging. The other suggestions are for improving code clarity and correctness within the newly added/modified files.

As an AI, I am not authorized to approve pull requests. Please ensure further review and approval from authorized maintainers after addressing the feedback.

#ifndef ENCHANTUM_ASSERT
#include <cassert>
// clang-format off
#define ENCHANTUM_ASSERT(cond, msg, ...) assert(cond && msg)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The ENCHANTUM_ASSERT macro is defined as assert(cond && msg), but it takes a variadic argument ... that isn't used in the expansion.

If the ... is intended for future enhancements (e.g., formatted messages), perhaps a comment could clarify this. Otherwise, if it's not planned to be used, would simplifying the macro to #define ENCHANTUM_ASSERT(cond, msg) assert(cond && msg) be more straightforward?

@ZXShady ZXShady force-pushed the enchantum_enum_reflection branch from cf254d3 to 0832143 Compare May 28, 2025 09:21
// To change the default minimum range for all enum types, redefine the macro
// RFL_ENUM_RANGE_MIN.
#if !defined(RFL_ENUM_RANGE_MIN)
#define RFL_ENUM_RANGE_MIN -256
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The range (-256, 256) strikes me as rather odd, since it doesn't match the previous implementation, and also goes over the one-byte "boundary" of an underlying type. I haven't looked at your enchantum internals yet, but I suspect that this new > 9-bit enum range may have some performance implications.

Copy link
Contributor Author

@ZXShady ZXShady May 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my library clamps the enum ranges so it doesn't matter.

I did a small test with magic enum, mine, and rfl in this gist

https://gist.github.com/ZXShady/74ebfe7d14ddcfeee6b0577a9ee4a396

time to compile

magic enum : 9 seconds

enchantum : 2.5 seconds

rfl : 19 seconds

and by performance you mean what exactly? compile time or runtime?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both compile time and also memory, but I suppose that would be driven by the underlying type more than the enum range?

Copy link
Contributor Author

@ZXShady ZXShady May 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both compile time and also memory, but I suppose that would be driven by the underlying type more than the enum range?

no the enum range determines the compile times. see my library readme

but if enum range exceeds underlying type min max it clamps the range.

the compile time are faster (see gist above) and memory usage should be the same, memory usage while compiling though is heavily reduced just like compile times

@liuzicheng1987
Copy link
Contributor

Unless the tests reveal some compiler/OS-specific problems we haven't discovered so far (unlikely), this is ready to be merged, IMO.

@ZXShady , do you agree?

@ZXShady , I have one more request: When you upload enchantum to vcpkg and Conan, please notify me. The maintainers of these package managers want to avoid us including thirdparty libraries that they also ship. If people have two different versions of the same library in their code and don't even know about it that can cause all sort of problems...

@ZXShady
Copy link
Contributor Author

ZXShady commented May 29, 2025

Unless the tests reveal some compiler/OS-specific problems we haven't discovered so far (unlikely), this is ready to be merged, IMO.

@ZXShady , do you agree?

I was thinking about using the split headers instead of the big one since I always keep forgetting to update it and it bloats compile times what do you think?
and we would have to wait for the tests.

I don't think you should merge now I think I will update the header file again since I am releasing version 0.1.0 today that removes some overloads.

@ZXShady , I have one more request: When you upload enchantum to vcpkg and Conan, please notify me. The maintainers of these package managers want to avoid us including thirdparty libraries that they also ship. If people have two different versions of the same library in their code and don't even know about it that can cause all sort of problems...

I am thinking of writing a vcpkg port.

@liuzicheng1987
Copy link
Contributor

@ZXShady , there appears to be one more issue on macOS:

Error: /Users/runner/work/reflect-cpp/reflect-cpp/include/rfl/internal/enums/../../thirdparty/enchantum.hpp:1490:19: error: no member named 'format_to' in namespace 'std'
[818](https://github.com/getml/reflect-cpp/actions/runs/15320861147/job/43104128808?pr=441#step:8:819)
      return std::format_to(ctx.out(), "{}", enchantum::to_string_bitflag(e));
[819](https://github.com/getml/reflect-cpp/actions/runs/15320861147/job/43104128808?pr=441#step:8:820)
             ~~~~~^
[820](https://github.com/getml/reflect-cpp/actions/runs/15320861147/job/43104128808?pr=441#step:8:821)
Error: /Users/runner/work/reflect-cpp/reflect-cpp/include/rfl/internal/enums/../../thirdparty/enchantum.hpp:1492:19: error: no member named 'format_to' in namespace 'std'
[821](https://github.com/getml/reflect-cpp/actions/runs/15320861147/job/43104128808?pr=441#step:8:822)
      return std::format_to(ctx.out(), "{}", enchantum::to_string(e));
[822](https://github.com/getml/reflect-cpp/actions/runs/15320861147/job/43104128808?pr=441#step:8:823)

I am not entirely sure why this happening, because you are clearly including <format>. Maybe the problem will go away once you swap to the multiple header files.

I am totally fine with splitting up the single header file. By the way, if you keep on forgetting to update it, you can write a simple Python script that does it for you and then have a GitHub Actions step automatically file a PR.

@liuzicheng1987
Copy link
Contributor

liuzicheng1987 commented May 29, 2025

@ZXShady , by the way, I am also taking a closer look at the build pipelines, specifically why they suddenly take so long. The issue appears to be that GitHub Actions fails to connect to the binary cache and then tries to do so over and over again.

@ZXShady
Copy link
Contributor Author

ZXShady commented May 29, 2025

Github sctions seems to be failing for me as well right now. been waiting and no runner been picked up

I split the headers and used the latest release

@liuzicheng1987

you are free to merge if the tests succeed

@ZXShady
Copy link
Contributor Author

ZXShady commented May 29, 2025

currently overriding enum_range doesn't allow prefix_length to be overriden as well.

I think it would be better to also add a recommendation to instead override enchantum::enum_traits than config::enum_range

@liuzicheng1987
Copy link
Contributor

@ZXShady what exactly does the prefix length do? I think it can be easily added to the configurations...

@ZXShady
Copy link
Contributor Author

ZXShady commented May 29, 2025

@liuzicheng1987

it removes N characters from the start of enum name.

example in docs

#include <enchantum/enchantum.hpp>
#include <enchantum/bitwise_operators.hpp>
// this is a bug enum it is outside of default range [-256,256] and it has this annoying prefix
enum BigEnumOutsideOfDefault : std::uint16_t {
  BigEnumOutsideOfDefault_A =0,BigEnumOutsideOfDefault_B = 4096 
};

template<>
struct enchantum::enum_traits<BigEnumOutsideOfDefault> {
  constexpr static auto prefix_length = sizeof("BigEnumOutsideOfDefault_")-1;
  constexpr static auto min = 0;
  constexpr static auto max = 4096;
};


// prefix removed
static_assert(enchantum::names<BigEnumOutsideOfDefault>[0] == "A");
static_assert(enchantum::names<BigEnumOutsideOfDefault>[1] == "B");

I anm myself against making aliases for existing things. if the user needs this he should just specialize enchantum's instead.

@liuzicheng1987
Copy link
Contributor

@ZXShady I See. I suppose that is something we can always add later.

@liuzicheng1987
Copy link
Contributor

@ZXShady the tests have finally succeeded. I don't why they take do long these days.

But I am merging. Thank you so much for this great contribution. If you really manage to extend the range to -32,000 to 32,000, please open another PR. I would be very interested.

@liuzicheng1987 liuzicheng1987 merged commit 750e67d into getml:main May 29, 2025
13 checks passed
@ZXShady
Copy link
Contributor Author

ZXShady commented May 29, 2025

@ZXShady the tests have finally succeeded. I don't why they take do long these days.

But I am merging. Thank you so much for this great contribution. If you really manage to extend the range to -32,000 to 32,000, please open another PR. I would be very interested.

The extended range will just require updating the header files btw.

(I have actually implemented it currently for clang, it is still local though on my machine though, results are very promising)

Thanks for accepting my contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants