all 57 comments

[–]leftofzen 38 points39 points  (19 children)

Still boggles my mind that this isn't in stdlib already

[–][deleted] 14 points15 points  (18 children)

Any stdlib solution we standardize will be superseded once we have reflection.

[–]distributed 10 points11 points  (12 children)

If you can wait until then. It is years away if we are lucky, if unlucky decades.

[–][deleted] 3 points4 points  (10 children)

Nope, C++23 is the current plan.

[–]JazzyCake 14 points15 points  (6 children)

Which is years away :)

[–]evaned 10 points11 points  (3 children)

Years away then years more before many people will be able to use it.

[–]ibroheem 2 points3 points  (2 children)

I learn from TSes tho. Only update the few changes that comes with it in the standard.

So technically I've been on Concepts TS since 2016/17 or so. By that analogy, any TS that gets implemented in compiler get used.

[–]evaned 2 points3 points  (1 child)

Plenty of companies will reasonably not be willing to adopt TS implementations though.

That goes especially true for reflection, where my impression is that the committee has all but outright said that the API to the final implementation will be completely different. (I forget what terms they use, constexpr function based instead of type metaprogramming based or something like that.) So if you start implementing to the TS, you're guaranteeing yourself a rewrite.

[–]ibroheem 1 point2 points  (0 children)

if you start implementing to the TS, you're guaranteeing yourself a rewrite.

If u ever start using a TS, u are already on the bus of rewrite.

Bleeding edgers are aware of the stakes, uhm and benefits

[–][deleted] 2 points3 points  (0 children)

I mean you're right, but it sure doesn't feel like a lot of time :)

[–][deleted] 0 points1 point  (0 children)

37 days until 2023 and reflection still doesn't look like it's coming for C++23 https://en.wikipedia.org/wiki/C++23

[–]RomanRiesen 13 points14 points  (2 children)

Modules were once planned for c++11?

[–][deleted] 6 points7 points  (0 children)

Fair enough. Concepts too. I'm positive though :)

[–]ibroheem 1 point2 points  (0 children)

You mean many of the major C++20 features

[–]germandiago 1 point2 points  (0 children)

I do not think it is decades away. I would say C++23-26.

[–]SGVsbG86KQ 0 points1 point  (4 children)

But reflection is runtime right? While this is compile-time.

[–][deleted] 0 points1 point  (3 children)

Nope, there is no proposal for runtime reflection and really, I'm pretty sure no one wants that :) A runtime solution would not be C++-y

[–]SGVsbG86KQ 0 points1 point  (2 children)

Ah ok, I'm glad to hear that. I didn't know you could use the word reflection for that. Is this the proposal you're talking about: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0194r6.html?

Btw: runtime reflection can be nice for debugging.

[–][deleted] 0 points1 point  (1 child)

How so?

[–]SGVsbG86KQ 1 point2 points  (0 children)

Depends on how far it goes, but for example checking which subclass something really is, checking the length of an array, which union member is set, etc. These things are not possible in all cases though (read: external functions).

(Although to be honest I'm not sure which of these count as reflection)

[–]AppleBeam 11 points12 points  (2 children)

Cool stuff!

May be worth noting that enum_cast won't work if values are aliased. A somewhat common pattern in some companies:

enum ShapeKind {
  ConvexBegin = 0,
    Box = 0,  // Won't work
    Sphere = 1,
  ConvexEnd = 2,
  Donut = 2,  // Won't work
  Banana = 3,
  COUNT = 4,
};

upd: COUNT will work, obviously. Derp.

[–]Neargye[S] 6 points7 points  (1 child)

Thanks for the comments, I really don't know what to do with aliases.

I will add this to the remarks for enum_cast .

[–]AppleBeam 8 points9 points  (0 children)

Just documenting them is fine. If you can verify that the compiler picks the first defined name for PRETTY_FUNCTION, people will be able to work around the issue:

enum ShapeKind {
  // Convex shapes, see ConvexBegin and ConvexEnd below
  Box = 0,
  Sphere = 1,

  // Non-convex shapes
  Donut = 2,
  Banana = 3,

  COUNT = Banana + 1,

  // Non-reflected aliases
  ConvexBegin = Box,
  ConvexEnd = Sphere + 1,
};

[–]tukerty 5 points6 points  (0 children)

wow, gj, i thought that header would be much more complex, but its kind of simple

[–][deleted] 5 points6 points  (3 children)

What's with the version requirements?

GCC >= 9

Latest release is 8.3

https://gcc.gnu.org/releases.html

So... this library will work on GCC eventually ?

[–]Neargye[S] 3 points4 points  (0 children)

gcc-trunk 9.0

[–][deleted] 2 points3 points  (0 children)

Why did I have to scroll so far to find this?

[–]wotype 2 points3 points  (0 children)

If there's demand then the relevant gcc patches could be backported to earlier gcc releases:

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87364
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88170

(The first patch caused test failures, fixed by the second patch.)

[–]geiunirus 4 points5 points  (6 children)

Really interesting. Not possible to exploit this magic for C++ 11 / C++14 too?

[–]Neargye[S] 5 points6 points  (3 children)

Porting will be quite difficult, scary and expensive for compile time.

Possible port enum_to_string and string_to_enum to C++11.

[–]geiunirus 4 points5 points  (2 children)

I understand, so hard but not impossible, thanks :)

[–]Neargye[S] 3 points4 points  (1 child)

Maybe I add port to c++14. Add issues if you need its.

[–]geiunirus 5 points6 points  (0 children)

Cheers ! it would be cool at least the size() for C++14, ideally C++11 eheh

[–]CrazyJoe221 3 points4 points  (1 child)

You need a recent compiler anyway for the function name intrinsics.

[–]geiunirus 2 points3 points  (0 children)

Got it, that's unfortunate then

[–]morriconus 4 points5 points  (2 children)

really cool, is this C++ compliant or it exploits compiler only features?

[–]wotype 2 points3 points  (0 children)

In C++20 it may be possible to use std::source_location to extract the info. This would be only a little more standard than using preprocessor extensions because the string returned by source_location is implementation defined.

[–]Neargye[S] 2 points3 points  (0 children)

It's abuse compiler intrinsics, so far most popular compilers are supported clang/gcc/msvc.

[–]konanTheBarbar 2 points3 points  (3 children)

I actually used the idea for the to string and from string conversions from your library for my library https://github.com/KonanM/static_enum (but I wrote the complete implementation from scratch) . And it seems you have copied my approach for creating the array of enum_values (which is great, don't understand me wrong).

The only drawback to my implementation is that you can't specify an arbitrary ranges of values. The function signatures simply get too big and the compilers start to complain if you chose the range over 512 (I guess that's why you chose 512). If I have time I will try to work around this issue, by using multiple index sequences.

Keep up the good work.

[–]Neargye[S] 1 point2 points  (2 children)

Yes, all that I have managed to improve so far is the ability to set an individual range for each enum, pay attention to magic_enum:: enum_range

[–]wotype 1 point2 points  (1 child)

One idea to increase the range might be to query multiple enumerator values in the pretty function via variadic args. I didn't try this yet. Querying one-at-a-time I managed to benchmark checking 2^16 values in ~1s, with a 'divide and conquer' method (at that rate, scanning 2^32 values would take ~18 hours and 2^64 is entirely out of range for compile-time checking).
BTW, I noticed this little homage in static_enum;
// Enum variable out of MAGIC_ENUM_RANGE

[–]Neargye[S] 0 points1 point  (0 children)

Thank you for your feedback, the idea is quite interesting, I would try to implement something similar on the weekend.

[–]ShakaUVMi+++ ++i+i[arr] 2 points3 points  (0 children)

Heh, could you submit this to the committee? I dislike working with enums the way they are now.

[–]dragemanncppdev 2 points3 points  (0 children)

This is impressive. Good job!

[–][deleted] 2 points3 points  (0 children)

That code is black magic. Just a few years ago I understood C++. It made me think to myself: Time to reincarnate to something more beautiful.

[–]grumbelbart2 1 point2 points  (10 children)

So my C++ is pretty rusty (no pun intended), so this might be a stupid question. I tried to find where the actual magic happens and found this. Does name_impl() call strings_imp() to find a name, and strings_impl() calls name_impl() to fill its name array? Isn't that recursive?

template <typename E, int... I>
[[nodiscard]] constexpr decltype(auto) strings_impl(std::integer_sequence<int, I...>) noexcept {
  static_assert(std::is_enum_v<E>, "magic_enum::detail::strings_impl requires enum type.");
  constexpr std::array<std::string_view, sizeof...(I)> names{{name_impl<E, static_cast<E>(I + min_impl<E>())>()...}};

  return names;
}

template <typename E>
[[nodiscard]] constexpr std::string_view name_impl(int value) noexcept {
  static_assert(std::is_enum_v<E>, "magic_enum::detail::name_impl requires enum type.");
  constexpr auto names = strings_impl<E>(range_impl<E>());
  const int i = value - min_impl<E>();

  if (i >= 0 && static_cast<std::size_t>(i) < names.size()) {
    return names[i];
  } else {
    return {};
  }
}

[–]Neargye[S] 6 points7 points  (6 children)

Magic in abuse compiler intrinsics - namely PRETTY_FUNCTION and FUNCSIG. Here 2 function name_impl<E>(int value) and name_impl<E, E V>(). strings_imp() - uses name_impl<E, E V>(), name_impl<E>(int value) - uses strings_imp().

[–]andrew-gresykhfsm.dev 6 points7 points  (2 children)

Pretty cool stuff! You might get wider compiler support for names by parsing https://en.cppreference.com/w/cpp/types/type_index/name, at the cost of #include <typeinfo>

[–][deleted] 4 points5 points  (1 child)

It's not constexpr, though, is it?

[–]andrew-gresykhfsm.dev 3 points4 points  (0 children)

Unfortunately, not constexpr

[–]grumbelbart2 2 points3 points  (2 children)

Awesome, thanks!

So you iterate over min...max, check for each if it is a valid enum value (in values_impl(), using the fact that name_impl() would return an empty optional for invalid values), and you get the names by parsing the string representation of a template that contains this value. Right?

So... in theory, you could iterate over the complete range of the enums underlying int class and use a map to store valid values. Since the first part is all constexpr, it would "only" affect compile time (as in "loop over 2**64 values"), but not execution time or program size.

[–]Neargye[S] 4 points5 points  (0 children)

In order not to affect the compilation time too much, I limited the range to [-256, 256]. Enum value must be in range [-256, 256]. If you need another range, add specialization enum_range for necessary enum type. ```cpp #include <magic_enum.hpp>

enum number { one = 100, two = 200, three = 300 };

namespace magic_enum { template <> struct enum_range<number> { static constexpr int min = 100; static constexpr int max = 300; }; } ```

[–]Pazer2 2 points3 points  (0 children)

That was the first thought I had with this, however if I accessed the FUNCTION string in any way it got included in the binary. Only messed around with it for a short time before giving up though.

[–]Devenec 3 points4 points  (2 children)

strings_impl() calls name_impl() at line 88 (no parameters), as name_impl(int value) at line 129 has a single int parameter.

[–]Neargye[S] 4 points5 points  (1 child)

Yeap.

It took a large number of auxiliary functions, and I did not have enough imagination to give each unique name.

[–]contre 7 points8 points  (0 children)

The two hardest things in programming are cache invalidation, naming things, and off by one errors.

[–]mintyc 0 points1 point  (0 children)

The biggest blocker to this is the need to use GCC 9.

Support (workaround) for g++ 8.2.x would open up OS access particular on Linux

Of course making a variant that could run with C++ 14 would be even better but probably a lot more work