C++ enum.toString() and then some -- or "preprocessor metaprogramming is like, 'wow!', man"
So I have a simple enough problem. I have a series of named data objects
class Base { | |
const wchar_t * _name; | |
public: | |
Base(const wchar_t * name) : _name(name) {} | |
const wchar_t * name(void) const { return _name; } | |
}; | |
template<class T> | |
class D : public Base { | |
public: | |
D(const wchar_t * name, const O &init) : Base(name), value(init) {} | |
T value; | |
}; |
and a container class
class Items | |
{ | |
public: | |
Items(void); | |
virtual ~Items(void); | |
Initialise(void); | |
std::array<std::shared_ptr<Base>, COUNT> values; | |
}; |
where there are COUNT
of these items, each with a name, type and default value, and the Initialise
method where heap allocations are done outside the constructor -- it simply fills in the array with appropriately typed named values with their default values, and I want client code to be able to index the array with symbolic names.
Plain enums don't have a to-string (though there are tricks to auto-associate names with strings by simple preprocessor metaprogramming); to get a compile-time count there's the old stand-by of putting an extra dummy member at the end and a big fat comment about inserting new values above it.
C++11 enum classes don't give me anything over the older enums here. There are various enum-like classes out there, in varying degrees of complexity; these give type-safety (which I don't really need in this context), could be extended with extra data, can be iterated over -- but don't give a compile-time count.
Time to break out Boost for some industrial grade tooling, and be very glad that my compiler supports variadic macros.
I want to define my items one time, one place, no extras like
#define ENUMS \ | |
((First, int, 0), \ | |
(Second, bool, false), \ | |
(Third, int, 42)) |
Let's start with basing our enum representation off the Boost MPL wrapper for size_t
#define MAKE_ENUM(N, NAME, T, DEFAULT) \ | |
class NAME : public boost::mpl::size_t<N> { \ | |
public: \ | |
typedef T rep_type; \ | |
static const wchar_t * const label; \ | |
static const T default_value; \ | |
}; |
with static initialization
#define _L(NAME) \ | |
BOOST_PP_CAT(L, NAME) | |
#define INIT_ENUM_STATIC(N, NAME, T, DEFAULT) \ | |
const wchar_t * const NAME::label = _L(BOOST_PP_STRINGIZE(NAME)); \ | |
const T NAME::default_value = DEFAULT; |
and from there it's just a case of using the preprocessor metaprogramming to put the bits together, just iterating over the definitions to define all the types, their static initialization and the array initialization:
#define MAKE_ENUM_I(R, DATA, N, ENUM_TUPLE) \ | |
MAKE_ENUM(N, BOOST_PP_TUPLE_ELEM(0, ENUM_TUPLE), BOOST_PP_TUPLE_ELEM(1, ENUM_TUPLE), BOOST_PP_TUPLE_ELEM(2, ENUM_TUPLE)) | |
#define INIT_ENUM_STATIC_I(R, DATA, N, ENUM_TUPLE) \ | |
INIT_ENUM_STATIC(N, BOOST_PP_TUPLE_ELEM(0, ENUM_TUPLE), BOOST_PP_TUPLE_ELEM(1, ENUM_TUPLE), BOOST_PP_TUPLE_ELEM(2, ENUM_TUPLE)) | |
#define INIT_ENUM(R, ARRAY, ELEM) \ | |
ARRAY[ELEM()] = std::shared_ptr<D<ELEM::rep_type>>(new D<ELEM::rep_type>(ELEM::label, ELEM::default_value)); | |
#define INIT_ENUM_T(R, ARRAY, ELEM) \ | |
INIT_ENUM(R, ARRAY, BOOST_PP_TUPLE_ELEM(0, ELEM)) | |
#define MAKE_ENUMS(ENUM_TUPLE) BOOST_PP_LIST_FOR_EACH_I(MAKE_ENUM_I, nullptr, BOOST_PP_TUPLE_TO_LIST(ENUM_TUPLE)) | |
#define INIT_ENUMS(ENUM_TUPLE) BOOST_PP_LIST_FOR_EACH_I(INIT_ENUM_STATIC_I, nullptr, BOOST_PP_TUPLE_TO_LIST(ENUM_TUPLE)) | |
#define COUNT_ENUMS(ENUM_TUPLE) BOOST_PP_TUPLE_SIZE(ENUM_TUPLE) | |
#define INITIALISE(ENUM_TUPLE, ARRAY) BOOST_PP_LIST_FOR_EACH(INIT_ENUM_T, ARRAY, BOOST_PP_TUPLE_TO_LIST(ENUM_TUPLE)) | |
#define INITIALISE_ENUMS(ARRAY) INITIALISE(ENUMS, ARRAY) |
Then in the Items.h header file where I defined D
and Items
, realise the common parts:
#define COUNT COUNT_ENUMS(ENUMS) | |
MAKE_ENUMS(ENUMS) |
and the Items.cpp file realises the initialisations
// define enum statics | |
INIT_ENUMS(ENUMS) | |
Items::Initialise(void) | |
{ | |
// default-fill array | |
INITIALISE_ENUMS(values) | |
} |
and consumers have a tolerable symbolic constant access to the values
auto x = *(D<int> *)(items.values[Third()].get()); | |
std::wcout << x.name() << L" = " << x.value << std::endl; |
while extending the list only has to happen in one place.
I'm just not sure if it's a bit too magic for the purpose.
No comments :
Post a Comment