I've started playing around with Boost's PP lib with an aim to write some small tool to help convert enumerations to strings.
I've manged to get a solution where enums defined with a macro will also generate a complementary ToString function.
I've played with various syntax styles, but I was unable to get one particular arrangement to work.
This is what I have.
namespace Examples
{
// This will define an enum class with an underlying uint8_t type
ENUM_W_STR
(fruit, uint8_t,
(apple)
(banana)
);
// Like above but with a different construction syntax
ENUM_W_STR_2
(fruit2, uint8_t,
apple,
banana
);
// This variant allows values to be assigned
ENUM_W_STR_VAL
(fruit3, uint8_t,
(apple) (1)
(banana) (2)
(orange) (3)
);
}
but, this is what I would like.
ENUM_W_STR_2
(fruit2, uint8_t,
apple = 10,
banana = 20
);
I had issues with splitting the 'apple = 10' string when generating the switch statement in ToString. i.e., I could construct the enum but not the ToString function.
Can this be achieved? If not with Boost PP, then some other means?
Here is the source code (g++ 4.7.2, boost 1.50; clang 3.3 boost 1.57)
#include <boost/preprocessor.hpp>
#include <iostream>
namespace EnumUtils{
#define ENUMUTLS_STR_SWITCH_CASE(r, data, elem)\
case data::elem : return BOOST_PP_STRINGIZE(elem);
#define ENUMUTIL_EVEN(r, data,i, v) BOOST_PP_IIF(BOOST_PP_MOD_D(r, i, 2), ,v)
#define ENUMUTIL_ODD( r, data,i, v) BOOST_PP_IIF(BOOST_PP_MOD_D(r, i, 2),= v, )
#define ENUMUTIL_ENUM_LINE(r, data, i, v) ENUMUTIL_EVEN(r,data,i,v) ENUMUTIL_ODD(r,data,i,v) BOOST_PP_COMMA_IF(BOOST_PP_MOD_D(r,i,2))
#define ENUMUTLS_STR_SWITCH_CASE_VAL_BASE(r, data, i, v) BOOST_PP_IIF(BOOST_PP_MOD_D(r, i, 2),,ENUMUTLS_STR_SWITCH_CASE(r, data, v))
#define ENUMUTLS_STR_SWITCH_CASE_VAL( r, data, i, v) ENUMUTLS_STR_SWITCH_CASE_VAL_BASE(r,data,i,v)
#define ENUM_W_STR(name, type, seq)\
enum class name : type {\
BOOST_PP_SEQ_ENUM(seq)\
};\
inline const char* ToString(name v)\
{\
switch (v)\
{\
BOOST_PP_SEQ_FOR_EACH(\
ENUMUTLS_STR_SWITCH_CASE,\
name,\
seq)\
default: return "unknown " BOOST_PP_STRINGIZE(name);\
}\
}
#define ENUM_W_STR_2(name, type, args...)\
enum class name : type {\
BOOST_PP_SEQ_ENUM(BOOST_PP_VARIADIC_TO_SEQ(args))\
};\
inline const char* ToString(name v)\
{\
switch (v)\
{\
BOOST_PP_SEQ_FOR_EACH(\
ENUMUTLS_STR_SWITCH_CASE,\
name,\
BOOST_PP_VARIADIC_TO_SEQ(args))\
default: return "unknown " BOOST_PP_STRINGIZE(name);\
}\
}
#define ENUM_W_STR_VAL(name, type, seq)\
enum class name : type {\
BOOST_PP_SEQ_FOR_EACH_I(\
ENUMUTIL_ENUM_LINE,\
name,\
seq)\
};\
inline const char* ToString(name v)\
{\
switch (v)\
{\
BOOST_PP_SEQ_FOR_EACH_I(\
ENUMUTLS_STR_SWITCH_CASE_VAL,\
name,\
seq)\
default: return "unknown " BOOST_PP_STRINGIZE(name) ;\
}\
};
}
namespace Examples
{
ENUM_W_STR
(fruit, uint8_t,
(apple)
(banana)
);
ENUM_W_STR_2
(fruit2, uint8_t,
apple,
banana
);
ENUM_W_STR_VAL
(fruit3, uint8_t,
(apple) (1)
(banana) (2)
(orange) (3)
);
}
int main ()
{
std::cout << Examples::ToString(Examples::fruit::apple) << std::endl;
std::cout << Examples::ToString(Examples::fruit::banana) << std::endl;
std::cout << Examples::ToString(Examples::fruit2::apple) << std::endl;
std::cout << Examples::ToString(Examples::fruit2::banana) << std::endl;
std::cout << Examples::ToString(Examples::fruit3::apple) << std::endl;
std::cout << Examples::ToString(Examples::fruit3::banana) << std::endl;
return 0;
}
output:
apple
banana
apple
banana
apple
banana
This kind of trimming is possible using C++11 constexpr functions and a macro. Here is an outline of the approach:
#include <iostream>
constexpr const char terminators[] = " \t\r\n=";
// Note that this count includes the null terminator, which is deliberate.
constexpr size_t terminator_count = sizeof(terminators);
// Checks if a character is a terminator at compile time.
constexpr bool is_terminator(char c, size_t index = 0)
{
return
index >= terminator_count ? false :
c == terminators[index] ? true :
is_terminator(c, index + 1);
}
// Computes enum constant length at compile time.
constexpr size_t constant_length(const char *s, size_t index = 0)
{
return is_terminator(s[index]) ? index : constant_length(s, index + 1);
}
// Evaluates to characters from "from" up to length of "from".
constexpr char select(const char *from, size_t from_length, size_t index)
{
return index >= from_length ? '\0' : from[index];
}
constexpr const char *foo = "apple = 10";
constexpr size_t foo_length = constant_length(foo);
// You should use a macro to generate this, either from Boost, or your own.
// Ideally, the user would be able to change the maximum string length.
constexpr const char trimmed[] =
{select(foo, foo_length, 0), select(foo, foo_length, 1),
select(foo, foo_length, 2), select(foo, foo_length, 3),
select(foo, foo_length, 4), select(foo, foo_length, 5),
select(foo, foo_length, 6), select(foo, foo_length, 7)};
int main()
{
std::cout << foo << std::endl;
std::cout << trimmed << std::endl;
return 0;
}
The output is
apple = 10
apple
There is a limitation on the maximum length of the constant name, as you can see from how trimmed is defined. As it says, the maximum length should probably be controlled by an optional macro that the user could define if they run into problems. Of course, the default should be more than the 8 you see in my sketch above :)
If you are interested, I have just released an enum library whose declaration syntax is nearly the same as what you described in your question (though the internals are quite different): http://aantron.github.io/better-enums
I will be adding this kind of compile-time string trimming to the library shortly to make its to_string method constexpr – it is currently the only thing that the library is forced to delay until runtime.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With