8

Let's say I have a strongly typed enum type like this:

enum class message : int {
    JOIN = 0,
    LEAVE = 4,
    SPAWN = 1,
}

And I need to safely (safely in this case means discarding invalid variants) convert it from it's underlying type (int).

For this purpose, I have a function to convert it for me:

std::optional<message> get_message(int num) {
    return num == (int)message::JOIN || num == (int)message::LEAVE || num == (int)message::SPAWN ? (message)num : {};
}

This works, but is long to write and prone to mistakes, especially for enums with a larger number of variants.

Is there a way to automate this process in C++17?

0x400921FB54442D18
  • 725
  • 1
  • 5
  • 18
  • Any `int` value is a valid value for `message`. If you want to check whether value is one of the predefined constants then yes, you will need perform manual mapping. It can be simplified with macros. – user7860670 Aug 30 '19 at 19:33
  • Simply a `std::set` would do. A sorted `constexpr` array would also work well with binary search. – François Andrieux Aug 30 '19 at 19:34
  • @FrançoisAndrieux how would `std:set` help to filter out undefined values? – Remy Lebeau Aug 30 '19 at 19:35
  • A `switch` would be cleaner than `||` comparisons: `std::optional get_message(int num) { switch (num) { case message::JOIN: case message::LEAVE: case message::SPAWN: return static_cast(num); } return {}; }` but in general, you can't automate this unless your enum values are sequential, then you could simply use `>=` and `<=` comparisons on the lower/upper values. Otherwise, there is no reflection in C++, you will have to code the allowed values explicitly – Remy Lebeau Aug 30 '19 at 19:37
  • You also could use `boost::fusion`. – JulianW Aug 30 '19 at 19:37
  • 2
    If the enum is short, an "or" expression will do. If the largest value does not exceed 31 (63), test `(1 << num) & mask` where `mask` combines all allowed values. In other cases, use a map. –  Aug 30 '19 at 19:37
  • c++ doesn't have reflection, so you have to write the check yourself :/ – Jarod42 Aug 30 '19 at 19:39
  • @YvesDaoust except that in the example given, `JOIN` is 0, so it would not appear in a `mask` of allowed enum values, unless you shift the mask bits by 1 – Remy Lebeau Aug 30 '19 at 19:39
  • @RemyLebeau You put the list of allowed values in a global, static member or function local static `const` container and check if `num` is in that container. `std::set` or a sorted `std::array` are good options. The goal is to only list them once and to make it easy to understand. – François Andrieux Aug 30 '19 at 19:39
  • @FrançoisAndrieux ok thanks – Remy Lebeau Aug 30 '19 at 19:40
  • @RemyLebeau: for the given example, the mask is 10011. –  Aug 30 '19 at 19:40
  • @YvesDaoust hmm, ok. I thought you were referring to simply `mask = JOIN | LEAVE | SPAWN` instead, which wouldn't work. – Remy Lebeau Aug 30 '19 at 19:41
  • 1
    This is Neargye/magic_enum's `magic_enum::enum_cast`: https://github.com/Neargye/magic_enum , although that's not exactly efficient (it's doing a string comparision: https://godbolt.org/z/sItmZb ). Perhaps this magic_enum library could be used to implement this efficiently, though – Justin Aug 30 '19 at 19:47
  • @FrançoisAndrieux why not `std::unordered_set` ? – Aykhan Hagverdili Aug 30 '19 at 19:57
  • @Ayxan Sure, but unless you have a lot of enums it's probably not worth the overhead. – François Andrieux Aug 30 '19 at 19:58

1 Answers1

0

Talking about underlying type, we notice that this class merely obtains a type using another type as model, but it does not transform values or objects between those types.

As an option to simplify the function you could work by iterating in the enum,or as others said before with some type of container, by iterating the same enum as an example here: How can I iterate over an enum? and more information about enum just in case: https://learn.microsoft.com/en-us/cpp/cpp/enumerations-cpp?view=vs-2019