3
\$\begingroup\$

I have long been looking for an elegant, one-line solution to mapenum to strings automatically, for use with the<< and>> stream operators.

I know that a lot of macros have been proposed to achieve that, but I never found a really simple scheme, with only one macro call.

This is my attempt, and I would be interested to discuss the advantages and limitations of this approach...

Definitions:

#include <string>#include <iostream>#include <stdexcept>#include <algorithm>#include <iterator>#include <sstream>#include <vector>#define MAKE_STRING(str, ...) #str, MAKE_STRING1_(__VA_ARGS__)#define MAKE_STRING1_(str, ...) #str, MAKE_STRING2_(__VA_ARGS__)#define MAKE_STRING2_(str, ...) #str, MAKE_STRING3_(__VA_ARGS__)#define MAKE_STRING3_(str, ...) #str, MAKE_STRING4_(__VA_ARGS__)#define MAKE_STRING4_(str, ...) #str, MAKE_STRING5_(__VA_ARGS__)#define MAKE_STRING5_(str, ...) #str, MAKE_STRING6_(__VA_ARGS__)#define MAKE_STRING6_(str, ...) #str, MAKE_STRING7_(__VA_ARGS__)#define MAKE_STRING7_(str, ...) #str, MAKE_STRING8_(__VA_ARGS__)#define MAKE_STRING8_(str, ...) #str, MAKE_STRING9_(__VA_ARGS__)#define MAKE_STRING9_(str, ...) #str, MAKE_STRING10_(__VA_ARGS__)#define MAKE_STRING10_(str) #str#define MAKE_ENUM(name, ...) MAKE_ENUM_(, name, __VA_ARGS__)#define MAKE_CLASS_ENUM(name, ...) MAKE_ENUM_(friend, name, __VA_ARGS__)#define MAKE_ENUM_(attribute, name, ...) name { __VA_ARGS__ }; \    attribute std::istream& operator>>(std::istream& is, name& e) { \        const char* name##Str[] = { MAKE_STRING(__VA_ARGS__) }; \        std::string str; \        std::istream& r = is >> str; \        const size_t len = sizeof(name##Str)/sizeof(name##Str[0]); \        const std::vector<std::string> enumStr(name##Str, name##Str + len); \        const std::vector<std::string>::const_iterator it = std::find(enumStr.begin(), enumStr.end(), str); \        if (it != enumStr.end())\            e = name(it - enumStr.begin()); \        else \            throw std::runtime_error("Value \"" + str + "\" is not part of enum "#name); \        return r; \    }; \    attribute std::ostream& operator<<(std::ostream& os, const name& e) { \        const char* name##Str[] = { MAKE_STRING(__VA_ARGS__) }; \        return (os << name##Str[e]); \    }

Usage:

enum MAKE_ENUM(Test3, Item13, Item23, Item33, Itdsdgem43);class Essai {public:    enum MAKE_CLASS_ENUM(Test, Item1, Item2, Item3, Itdsdgem4);};int main() {    std::cout << Essai::Item1 << std::endl;    Essai::Test ddd = Essai::Item1;    std::cout << ddd << std::endl;    std::istringstream strm("Item2");    strm >> ddd;    std::cout << (int) ddd << std::endl;    std::cout << ddd << std::endl;}
Jamal's user avatar
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
askedMar 7, 2014 at 16:53
OlivierB's user avatar
\$\endgroup\$
4
  • \$\begingroup\$Shouldn'tEssai just be astruct since its only member ispublic?\$\endgroup\$CommentedMar 7, 2014 at 19:10
  • \$\begingroup\$You may want to see this template version I wrote.codereview.stackexchange.com/a/14315/507 It maps enum to strings and strings back to enums. All you need to do is define an array of strings for each enum and the template magic does the rest.\$\endgroup\$CommentedMar 8, 2014 at 2:07
  • \$\begingroup\$There are only a few real uses for macros in C++ this is not one of them.\$\endgroup\$CommentedMar 8, 2014 at 2:12
  • \$\begingroup\$@Jamal: it's just an example to show an usage within a class... @LokiAstari: your template version is really nice. But, it requires conversion functionsenumToString() andenumFromString(). The goal here was to provideenum directly with the stream serialization operators<< and>>.\$\endgroup\$CommentedMar 8, 2014 at 10:13

1 Answer1

2
\$\begingroup\$

I agree with the comments: macros are a mess. Avoid them when possible.

If you're willing to make certain sacrifices (a little repetition), it's possible to avoid macros here. Doing so will also avoid some of the arbitrary limitations in your macro implementation, such as the 10 or so cap on enum values.

Using C++11, it's not hard to wire upLoki Astari's approach tooperator<< andoperator>> with some type traits template metaprogramming. I personally think enums are much cleaner with the strongly typedenum class ... variant introduced by C++11, so I would suggest that anyway, but theenum_serializable approach I show here works on both strongly and weakly typed enums.

#include <type_traits>#include <iostream>#include <https://codereview.stackexchange.com/a/14315/507 by reference>template <typename Enum>struct enum_serializable : std::false_type{};// enable operator<< and operator>> for `Essai` valuestemplate <>struct enum_serializable<Essai> : std::true_type{};template <typename Enum>typename std::enable_if<enum_serializable<Enum>::value, std::ostream&>::type operator<<(std::ostream& os, Enum e){    return os << enumToString(e);}template <typename Enum>typename std::enable_if<enum_serializable<Enum>::value, std::istream&>::type operator>>(std::istream& is, Enum& e){    return is >> enumFromString(e);}

It's also not hard to pull Loki Astari's approach into theoperator<< andoperator>> functions above, removing the separateenumToString andenumFromString functions and theenumRefHolder andenumConstRefHolder structs entirely.

This general approach should be available before C++11 as well, butstd::enable_if will have to be implemented instead of#included.

answeredMar 9, 2014 at 15:46
Michael Urman's user avatar
\$\endgroup\$

You mustlog in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.