Reputation: 1604
Here is my source (as answer) , how to implement underlying_value, and to_enum functions. How to automatically convert strongly typed enum into int?
underlying_value - no problem. but, to_enum - has problem.
see:
enum class E{ a = 1, b = 3, c = 5 };
auto e_a = utils::underlying_value(E::a); //OK
E t = utils::to_enum<E>( 2 ) ; // compiled, but it's incorrect. I think here must throws exception?
Q: how to implement to_enum correctly?
Upvotes: 1
Views: 378
Reputation: 3847
Despite the comments on the question, this can be done in C++11, though to do it without code repetition you will eventually have to wrap the enum class
declaration in a macro. That may make my answer unsuitable, depending on your needs. Either way, doing the checked conversion requires some machinery, so I will get to the macro last.
The basic idea is to use constexpr
functions to scan an array:
#include <iostream>
#include <stdexcept>
enum class E { a = 1, b = 3, c = 5 };
constexpr E values[] = {E::a, E::b, E::c};
constexpr size_t count = sizeof(values) / sizeof(E);
constexpr E to_enum(int value, size_t index = 0)
{
return
index >= count ? throw std::runtime_error("invalid integer") :
static_cast<int>(values[index]) == value ? values[index] :
to_enum(value, index + 1);
}
constexpr E converted = to_enum(3);
// Will not compile if uncommented.
// constexpr E bad_converted = to_enum(2);
int main()
{
std::cout << static_cast<int>(converted) << std::endl;
return 0;
}
This prints 3
. If the line with bad_converted
is uncommented, this code will not compile at all, as it says. The checked conversion can be done either at runtime or during compilation. It will be done during compilation if the argument to to_enum
is a compile-time constant. Also, as you can probably see, this does a linear scan of values
, but that can be replaced with another algorithm if it becomes a performance problem for a very large enum.
The code I just showed is a sketch that shows the underlying method. In order to make this less of a pain to use, you should wrap the declaration of E
in a macro that will automatically generate the values[]
array and the associated functions. I will show and rationalize the contents of this macro one point at a time.
The basic macro looks like this
// Declaration header
#define ENUM_CLASS(TypeName, __VA_ARGS__)
// Use
ENUM_CLASS(E, a = 1, b = 3, c = 5);
So, in this example, __VA_ARGS__
will be the tokens a = 1, b = 3, c = 5
. So, we can declare the enum itself inside the macro as follows:
enum class TypeName { __VA_ARGS__ };
However, we cannot simply declare:
constexpr TypeName values[] = { __VA_ARGS__ };
because that expands to
constexpr TypeName values[] = { a = 1, b = 3, c = 5 };
which is not scoped (missing TypeName::
in front of each value), and is not valid C++ because of the extra assignment operators inside the array initializer. I will solve the second problem first. You need to define a class like this one:
template <typename E>
class swallow_assignment {
public:
E _value;
constexpr explicit swallow_assignment(E value) : _value(value)
{
}
template <typename Any>
constexpr const swallow_assignment& operator =(Any other) const
{
return *this;
}
constexpr operator E() const
{
return _value;
}
};
Now, you can write (swallow_assignment<E>)E::a = 1
. What will happen is, at compile time, E::a
will get converted to the assignable value (swallow_assignment<E>)E::a
, which has the same internal representation as E::a
. That value will then ignore the assignment of 1
, and then will be converted back to E::a
.
What's left is to prefix each of the declared constants so that we get
constexpr TypeName values[] =
{(swallow_assignment<E>)E::a = 1,
(swallow_assignment<E>)E::b = 3,
(swallow_assignment<E>)E::c = 5})
which will now be a valid initializer. This can be done with a mapping macro. I won't go into the details here, because that is a whole separate topic, but such a macro can be found here https://github.com/aantron/better-enums/blob/e28177b11a9e3d7152c5216d84fdf8939aff0eba/enum_preprocessor_map.h. Boost might also have a better one. Whatever macro you are using, I will assume that its signature is PP_MAP(prefix, __VA_ARGS__)
. The sketch for the final macro definition for the whole enum then becomes:
#define ENUM_CLASS(TypeName, __VA_ARGS__) \
enum class TypeName { __VA_ARGS__ }; \
constexpr TypeName values[] = \
{ PP_MAP((swallow_assignment<TypeName>)TypeName::, \
__VA_ARGS__) }; \
constexpr size_t count = sizeof(values) / sizeof(TypeName);
You will probably want to stuff these definitions into a specialization of a traits type, so that you can use this macro with more than one enum class
(otherwise the arrays named values
will collide). You may have to use weak symbols to avoid linking problems if you make values
a static member of a traits class, however.
These last points are left as an exercise, because this answer is already way too long :) I have a library which does all of the above, though it wraps an enum
instead of providing a traits specialization for an enum class
. There is an unpublished branch with a combination of enum class
/traits, however. You can see the library here: http://aantron.github.io/better-enums. The library's ::_from_integral()
method corresponds to the to_enum
function in your question, and it does both run-time and compile-time conversions.
Upvotes: 1