EnumFormatter.h 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. // Copyright 2021 Dolphin Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #pragma once
  4. #include "Common/EnumMap.h"
  5. #include <fmt/format.h>
  6. #include <type_traits>
  7. /*
  8. * Helper for using enums with fmt.
  9. *
  10. * Usage example:
  11. *
  12. * enum class Foo
  13. * {
  14. * A = 0,
  15. * B = 1,
  16. * C = 2,
  17. * };
  18. *
  19. * template <>
  20. * struct fmt::formatter<Foo> : EnumFormatter<Foo::C>
  21. * {
  22. * constexpr formatter() : EnumFormatter({"A", "B", "C"}) {}
  23. * };
  24. *
  25. * enum class Bar
  26. * {
  27. * D = 0,
  28. * E = 1,
  29. * F = 3,
  30. * };
  31. *
  32. * template <>
  33. * struct fmt::formatter<Bar> : EnumFormatter<Bar::F>
  34. * {
  35. * // using std::array here fails due to nullptr not being const char*, at least in MSVC
  36. * // (but only when a field is used; directly in the constructor is OK)
  37. * static constexpr array_type names = {"D", "E", nullptr, "F"};
  38. * constexpr formatter() : EnumFormatter(names) {}
  39. * };
  40. */
  41. template <auto last_member>
  42. class EnumFormatter
  43. {
  44. using T = decltype(last_member);
  45. static_assert(std::is_enum_v<T>);
  46. public:
  47. constexpr auto parse(fmt::format_parse_context& ctx)
  48. {
  49. auto it = ctx.begin(), end = ctx.end();
  50. // 'u' for user display, 's' for shader generation, 'n' for name only
  51. if (it != end && (*it == 'u' || *it == 's' || *it == 'n'))
  52. format_type = *it++;
  53. return it;
  54. }
  55. template <typename FormatContext>
  56. auto format(const T& e, FormatContext& ctx) const
  57. {
  58. const auto value_s = static_cast<std::underlying_type_t<T>>(e); // Possibly signed
  59. const auto value_u = static_cast<std::make_unsigned_t<T>>(value_s); // Always unsigned
  60. const bool has_name = m_names.InBounds(e) && m_names[e] != nullptr;
  61. switch (format_type)
  62. {
  63. default:
  64. case 'u':
  65. if (has_name)
  66. return fmt::format_to(ctx.out(), "{} ({})", m_names[e], value_s);
  67. else
  68. return fmt::format_to(ctx.out(), "Invalid ({})", value_s);
  69. case 's':
  70. if (has_name)
  71. return fmt::format_to(ctx.out(), "{:#x}u /* {} */", value_u, m_names[e]);
  72. else
  73. return fmt::format_to(ctx.out(), "{:#x}u /* Invalid */", value_u);
  74. case 'n':
  75. if (has_name)
  76. return fmt::format_to(ctx.out(), "{}", m_names[e]);
  77. else
  78. return fmt::format_to(ctx.out(), "Invalid ({})", value_s);
  79. }
  80. }
  81. protected:
  82. // This is needed because std::array deduces incorrectly if nullptr is included in the list
  83. using array_type = Common::EnumMap<const char*, last_member>;
  84. constexpr explicit EnumFormatter(const array_type names) : m_names(std::move(names)) {}
  85. const array_type m_names;
  86. char format_type = 'u';
  87. };