Skip to content

please add an ADL version of customize::enum_range<> (code included) #381

@lsemprini

Description

@lsemprini

Hi, thanks so much for magic_enum!

As far as I can tell, the mechanism for turning on is_flags or other enum_range flags (specializing magic_enum::customize::enum_range<T>) only works from global scope (because C++ doesn't let us change magic_enum::customize:: unless the scope we are in (global) encloses magic_enum).

This is quite inconvenient when the enum being customized is deep inside one or more classes and/or namespaces. And it's a little dangerous too, because if we accidentally use the enum with magic_enum before the specialization, magic_enum will do the wrong thing. It's good to have the magic_enum customize code right after where the enum is defined.

ADL is exactly the right tool for this because it can give magic_enum access to whatever scope contains the enum (even if it's deep inside namespaces and classes)

So, I have a simple proposal to add a second, ADL-based mechanism for magic_enum to find enum_range parameters.

The idea is for magic_enum to also check for the ability to call the function magic_enum_enum_range(t) and if a call is possible, use the struct returned by the function in exactly the same way that magic_enum currently uses magic_enum::customize::enum_range<T> The function is never actually called at runtime (and it's constexpr) and it's never used in evaluated context, only used inside decltype() so it doesn't add any overhead.

Here is the code for how to do this to fetch is_flags (tested on MSVC19.41 with -std:c++latest, clang 18.0.2 with -std=c++2c, gcc 13.1.0 with -std=c++20)

Here is the existing, unmodified code from magic_enum.hpp:

template <typename T, typename = void>
struct has_is_flags : std::false_type {};

template <typename T>
struct has_is_flags<T, std::void_t<decltype(customize::enum_range<T>::is_flags)>> : std::bool_constant<std::is_same_v<bool, std::decay_t<decltype(customize::enum_range<T>::is_flags)>>> {};

Add one more struct to try the ADL case:

// you must define an ADL-reachable function magic_enum_enum_range(T t):
//
//    typedef enum e1 {} e1;
//    struct e1_enum_range
//    {
//        // here is the same stuff you would put into magic_enum::customize::enum_range<T>
//        inline static bool is_flags = true;
//    };
//    // include "friend" if this function is in a class
//    // exclude "friend" if this function is in a namespace (including global scope)
//    friend inline constexpr auto magic_enum_enum_range(e1) { return e1_enum_range(); }
//
template <typename T>
struct has_is_flags<T, std::enable_if_t< std::is_same_v< decltype(decltype(magic_enum_enum_range(T{}))::is_flags), bool > > > : std::true_type {};

Add this new get_is_flags struct to keep the code clean:

template <typename T, typename = void>
struct get_is_flags : std::false_type {};

// - return type already checked in has_is_flags
template <typename T>
struct get_is_flags<T, std::void_t<decltype(customize::enum_range<T>::is_flags)>> : std::bool_constant<customize::enum_range<T>::is_flags> {};

template <typename T>
struct get_is_flags<T, std::enable_if_t< std::is_same_v< decltype(decltype(magic_enum_enum_range(T{}))::is_flags), bool > > > : std::bool_constant< decltype(magic_enum_enum_range(T{}))::is_flags > {};

and then later on where the code used to say:

  } else if constexpr (has_is_flags<E>::value) {
    return customize::enum_range<E>::is_flags ? enum_subtype::flags : enum_subtype::common;

change it to:

  } else if constexpr (has_is_flags<E>::value) {
    return get_is_flags<E>::value ? enum_subtype::flags : enum_subtype::common;

and similar small changes for the other enum_range flags like range_min and range_max

Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions