I've seen scores of threads over the years on how you could wrap an enum or template a class with the enum to add addtional functionality.
Consider the use case. I have an enum. I want to be able to create an object of a class around the enum class so that I can
A simple way to do this is
enum class color : uint16_t {
unknown = 0,
red,
green,
blue,
};
class colorClass {
public:
colorClass(){ val = color::unknown; }
colorClass(color v)
:val(v)
{}
//copy, move, assignment ctors section
bool isRed(){ return val == color::red; }
// more such functions
std::string toString() {
using enum color;
switch (val) {
case red : return "red";
case blue: return "blue";
case green: return "green";
case unknown: [[fallthrough]];
default: return "unknown";
}
}
private:
color val;
};
This works just fine. Now if I want to restrict this class some more I can use templating and concepts
enum class color : uint16_t {
unknown = 0
red,
green,
blue,
};
template<typename E>
concept ValidColor = std::is_enum_v<E>;
template<ValidColor E>
class colorClass {
// Pretty much the same implementation?
};
My memory's a little fuzzy but I think I've seen some clever implementations for toString using underlying type. I'm not looking to use any 3rd party libs.
Do I have the "modern" way right? Apart from enforcing the usage of the enum does the addition of templating provide any benefits?
Edit: I'm not asking just about reflection (and thanks for all the answers in that direction) but how C++20 and C++23 can enhance this "enum with a few tricks" class
Despite the fact that the year is 2024 and I am using C++20, I still find myself using X macros for this as I have yet to find a better (non-external-library) solution:
#define DEFINE_ENUM_VAL(class_name, name) name,
#define DEFINE_ENUM_STR_CONVERSION(class_name, name) case class_name::name: return #name;
#define FOR_EACH_FOOBAR(DO) \
DO(FooBar, ONE) \
DO(FooBar, TWO) \
DO(FooBar, THREE)
enum class FooBar
{
FOR_EACH_FOOBAR(DEFINE_ENUM_VAL)
};
inline auto to_string(FooBar val) -> std::string
{
switch (val) {
FOR_EACH_FOOBAR(DEFINE_ENUM_STR_CONVERSION)
}
std::unreachable();
}
#undef FOR_EACH_FOOBAR
#undef DEFINE_ENUM_STR_CONVERSION
#undef DEFINE_ENUM_VAL
This is fairly easy to extend for any behaviors that can be derived from the enum type name and value identifiers. However, a lot of the surrounding boilerplate will have to be repeated for every enum type that you want to support (though I bet this could be genericized a bit further if you wanted to avoid that, at the cost of relying even more on preprocessor magic).