How does magic_enum library can output enum value as string with msvc?

Advertisements

The magic_enum library provides way to retrieve an enum value as its name, in the form of a string.
For instance:

#include <iostream>

#include "magic_enum.hpp"

enum class Dummy : uint16_t {
    first,
    second,
    third,
};

int main() {
    std::cout << magic_enum::enum_name<Dummy::first>()
              << std::endl;  // outputs "first"}

Does some can explain here how magic_enum actually implements enum_name with msvc?

I managed to reproduce this behavior with clang/gcc by passing the enum value to a template function and using __PRETTY_FUNCTION__ inside, for instance:

template<typename Enum_T, Enum_T Value_E> class EnumValueNameAsString
{
    static constexpr std::string_view Get()
    {
        auto constexpr end = std::string_view(__PRETTY_FUNCTION__).find_last_of(';');
        if constexpr (end == std::string_view::npos)
        {
            return std::string_view("Failure");
        }
        auto constexpr start
            = std::string_view(__PRETTY_FUNCTION__).find_last_of('=', end);
        if constexpr (start == std::string_view::npos)
        {
            return std::string_view(
                "Failure");
        }
        // 0 <= start < end
        if constexpr (end - start <= 2)
        {
            return std::string_view(
                "Failure");
        }
        return std::string_view(__PRETTY_FUNCTION__).substr(start + 2, end - start - 2);
    }

public:
    static constexpr std::string_view Name = Get();
};

But for C++, using __FUNCSIG__ instead of __PRETTY_FUNCTION__, I cannot achieve the expected result as __FUNCSIG__ actually is class std::basic_string_view<char,struct std::char_traits<char> > __cdecl EnumValueNameAsString<enum Dummy,0>::Get(void)
The enum value name is nowhere in the string.

>Solution :

It seems like __FUNCSIG__ only converts an enum non-type template argument to its name in the immediately enclosing function. It will leave the 0 in the struct as a 0 since that’s not the function being called.

The fix is to use the enum template argument in the static member function, or use a free function:

template<typename Enum_T, Enum_T Value_E>
constexpr std::string_view f() { return __FUNCSIG__; }

template<typename Enum_T, Enum_T Value_E>
class EnumValueNameAsString {
    template<Enum_T>
    static constexpr std::string_view Get() {
        return __FUNCSIG__;
    }
public:
    static constexpr std::string_view Name = Get<Value_E>();
};

static_assert(f<Dummy, Dummy::first>() ==
"class std::basic_string_view<char,struct std::char_traits<char> > __cdecl f<enum Dummy,Dummy::first>(void)"
);

static_assert(EnumValueNameAsString <Dummy, Dummy::first>::Name ==
"class std::basic_string_view<char,struct std::char_traits<char> > __cdecl EnumValueNameAsString<enum Dummy,0>::Get<Dummy::first>(void)"
);

Leave a ReplyCancel reply