123

If I have an enum like this:

enum Errors {
    ErrorA = 0,
    ErrorB,
    ErrorC,
};

Then I want to print it out to console:

Errors anError = ErrorA;
std::cout << anError; // 0 will be printed

But what I want is the text "ErrorA". Can I do it without using if/switch? And what is your solution for this?

ivaigult
  • 6,198
  • 5
  • 38
  • 66
tiboo
  • 8,213
  • 6
  • 33
  • 43

13 Answers13

87

Using map:

#include <map>
#include <string_view>

enum Errors {ErrorA=0, ErrorB, ErrorC};

std::ostream& operator<<(std::ostream& out, const Errors value){
    // prior to C++11, create a function outside instead of using a lambda
    static const auto strings = []{
        // prior to C++17, replace std::string_view with
        // std::string or const char*
        std::map<Errors, std::string_view> result;
    #define INSERT_ELEMENT(p) result.emplace(p, #p);
        INSERT_ELEMENT(ErrorA);     
        INSERT_ELEMENT(ErrorB);     
        INSERT_ELEMENT(ErrorC);             
    #undef INSERT_ELEMENT
        return result;
    };
    
    return out << strings[value];
}

Using array of structures with linear search:

#include <string_view>

enum Errors {ErrorA=0, ErrorB, ErrorC};

std::ostream& operator<<(std::ostream& out, const Errors value){
#define MAPENTRY(p) {p, #p}
    const struct MapEntry{
        Errors value;
        std::string_view str;
    } entries[] = {
        MAPENTRY(ErrorA),
        MAPENTRY(ErrorB),
        MAPENTRY(ErrorC),
        {ErrorA, 0}//doesn't matter what is used instead of ErrorA here...
    };
#undef MAPENTRY
    const char* s = 0;
    for (const MapEntry* i = entries; i->str; i++){
        if (i->value == value){
            s = i->str;
            break;
        }
    }

    return out << s;
}

Using switch/case:

#include <string>

enum Errors {ErrorA=0, ErrorB, ErrorC};

std::ostream& operator<<(std::ostream& out, const Errors value){
    return out << [value]{
    #define PROCESS_VAL(p) case(p): return #p;
        switch(value){
            PROCESS_VAL(ErrorA);     
            PROCESS_VAL(ErrorB);     
            PROCESS_VAL(ErrorC);
        }
    #undef PROCESS_VAL
    };
}

Test the solutions:

#include <iostream>

int main(int argc, char** argv){
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
    return 0;   
}
Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
SigTerm
  • 26,089
  • 6
  • 66
  • 115
  • 19
    -1. Just do a switch-case instead of using a hash-map. Increased complexity is not a good thing. – Simon Jul 27 '10 at 11:43
  • 8
    Good point. Next time I will :) But now I see that you've already edited your post to add the kind of functionality I was looking for. Good job! – Simon Jul 27 '10 at 14:37
  • 1
    what is #p? if in the third example instead of enum I use an enum class, is it possible to get only the enum string without the class name? – rh0x Nov 17 '16 at 13:22
  • 3
    `#p` is the preprocessor stringifying p. So calling `PROCESS_VAL(ErrorA)` will output: `case(ErrorA): s = "ErrorA"; break;`. – Nashenas Dec 01 '16 at 16:35
  • 1
    I don't consider it as an optimal solution: Reasons: 1) I have to maintain *twice* the `enum` values which I think is a *NO-GO*. 2) When I understands the solution correctly it works only for one `enum`. – Peter VARGA Jan 08 '17 at 16:55
  • @AlBundy What is the alternative then? – Domarius Mar 15 '18 at 01:05
  • 2
    the last swtich/case is just brilliant! – Cătălina Sîrbu Mar 30 '20 at 22:05
  • Do we really need `const` qualifier for the `Errors` argument ? – LRDPRDX May 10 '22 at 08:27
  • I recommend to add a "default:" to the switch/case with error handling, like log print etc. - just in case the enum is changing in the future. default: printf("unknown enum value: %d\n", value); – neflow Nov 28 '22 at 09:54
  • I've modernized the solutions for C++17. The solution with `map` no longer requires an `if` check for whether the map has been initialized, and the `switch` solution no longer requires an extra variable. @SigTerm I recommend removing the linear search solution from the answer because it is neither robust nor efficient. The `switch` solution should be just as good in virtually all cases, and it's more concise. – Jan Schultke Aug 29 '23 at 08:35
39

Use an array of strings with matching values:

// until C++11
const char * const         ErrorTypes[] =
// since C++11
constexpr const char*      ErrorTypes[] =
// since C++17
constexpr std::string_view ErrorTypes[] =
{
    "errorA",
    "errorB",
    "errorC"
};

// ...

cout << ErrorTypes[anError];

Also consider whether to make the array static, inline, or extern. The choice depends on where it's located and which C++ standard you're using.


EDIT: The solution above is applicable when the enum is contiguous, i.e. starts from 0 and there are no assigned values. It will work perfectly with the enum in the question.

To further proof it for the case that enum doesn't start from 0, use:

cout << ErrorTypes[anError - ErrorA];
Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
Igor
  • 26,650
  • 27
  • 89
  • 114
  • 6
    unfortunately, enum allows us to assign values to the elements. How does you approach work if you have non-contiguos enums, line 'enum Status { OK=0, Fail=-1, OutOfMemory=-2, IOError=-1000, ConversionError=-2000 }` (so you can later add IOErrors to the -1001-1999 range) – Nordic Mainframe Jul 27 '10 at 12:16
  • @Luther: Yes, this will work only with contiguous enums, **which most enums are**. In case that enum is non-contiguous you will need to use another approach, i.e. maps. But in case of contiguous enum I'd suggest to use this approach, and not to overcomplicate. – Igor Jul 27 '10 at 12:24
  • 2
    So, If my colleague adds NewValue to an enum and does not update the ErrorTypes array, then ErrorTypes[NewValue] yields what? And how do I handle negative enum values? – Nordic Mainframe Jul 27 '10 at 12:33
  • 2
    @Luther: You will have to keep ErrorTypes updated. Again, there is a tradeoff between the simplicity and universality, depends what's more important for the user. What is the problem with negative enum values? – Igor Jul 27 '10 at 12:37
  • 1
    Shouldn't this array be static for memory efficiency? and const for safety? – Jonathan Livni Nov 01 '10 at 10:16
  • Suggested to change "char *ErrorTypes[] =" to "const char *ErrorTypes[] =". My g++ fails with the error message " error: ISO C++ forbids converting a string constant to ‘char*’ [-Werror=write-strings]" without the "const" keyword. – peizon Sep 25 '20 at 07:58
17

Here is an example based on Boost.Preprocessor:

#include <iostream>

#include <boost/preprocessor/punctuation/comma.hpp>
#include <boost/preprocessor/control/iif.hpp>
#include <boost/preprocessor/comparison/equal.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/seq/size.hpp>
#include <boost/preprocessor/seq/seq.hpp>


#define DEFINE_ENUM(name, values)                               \
  enum name {                                                   \
    BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_VALUE, , values)          \
  };                                                            \
  inline const char* format_##name(name val) {                  \
    switch (val) {                                              \
      BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_FORMAT, , values)       \
    default:                                                    \
        return 0;                                               \
    }                                                           \
  }

#define DEFINE_ENUM_VALUE(r, data, elem)                        \
  BOOST_PP_SEQ_HEAD(elem)                                       \
  BOOST_PP_IIF(BOOST_PP_EQUAL(BOOST_PP_SEQ_SIZE(elem), 2),      \
               = BOOST_PP_SEQ_TAIL(elem), )                     \
  BOOST_PP_COMMA()

#define DEFINE_ENUM_FORMAT(r, data, elem)             \
  case BOOST_PP_SEQ_HEAD(elem):                       \
  return BOOST_PP_STRINGIZE(BOOST_PP_SEQ_HEAD(elem));


DEFINE_ENUM(Errors,
            ((ErrorA)(0))
            ((ErrorB))
            ((ErrorC)))

int main() {
  std::cout << format_Errors(ErrorB) << std::endl;
}
HelloGoodbye
  • 3,624
  • 8
  • 42
  • 57
Philipp
  • 48,066
  • 12
  • 84
  • 109
  • 2
    +1, This solution doesn't rely on an external tool, like the lua answer above, but is pure C++, it follows the DRY principle, and the user syntax is readable (if formatted correctly. BTW, you do not need the Backslashes when using DEFINE_ENUM, which looks a bit more natural, IMO) – Fabio Fracassi Jul 27 '10 at 12:29
  • 7
    @Fabio Fracassi: "This solution doesn't rely on an external tool" Boost is an external tool - non standard C++ library. Besides, it is a bit too long. Solution to a problem should be as simple as possible. This one doesn't qualify... – SigTerm Jul 27 '10 at 12:37
  • 2
    Actually it is all the you could put most of the code (in fact all of it except the actual definition) can be put into a single header. so this is actually the shortest solution presented here. And for boost being external, yes, but less so than a out of language script for preprocessing parts of the source as the lua script above is. Besides boost is so close to the standard that it should be in every C++ programmers toolbox. Just IMHO, of course – Fabio Fracassi Jul 27 '10 at 18:27
  • [I've removed the unneeded escaping of newlines in the macro invocation. They are not needed: a macro invocation can span multiple lines.] – James McNellis Dec 12 '11 at 21:08
  • The macro `DEFINE_ENUM` gives me the error ``multiple definition of `format_ProgramStatus(ProgramStatus)'`` when I try to use it. – HelloGoodbye Dec 18 '17 at 12:03
  • As I used the macro in from header file, the function `format_##name` was redefined in every compilation unit that used the header file, which caused the problem. Declaring the function as `inline` solved the problem. – HelloGoodbye Dec 19 '17 at 07:32
  • 2
    Very nice, the only thing that I see as an improvement is using `enum class` instead of the plain enum, which requires passing `name` in the switch generation (as `data`) and prepending `data::` to each line generated in `DEFINE_ENUM_FORMAT`. Other than that, it's an awesome solution, thanks! EDIT: also, I added an ostream operator to use it with streams instead of calling the format function. – afp Jan 29 '21 at 12:01
11

You can use a simpler pre-processor trick to first define the list of named constants and their values, and then use this both for the enum definition, and for the switch.

#include <iostream>
#include <utility>

#define ERROR_DEF_LIST(E) \
  E(ErrorA, 0x1) \
  E(ErrorB, 0x2) \
  E(ErrorC, 0x4)

enum Errors {
    #define ERROR_DEF_ENUM_DEF(name, value) name = value,
    ERROR_DEF_LIST(ERROR_DEF_ENUM_DEF)
};

static inline std::ostream & operator<<(std::ostream &o, Errors e) {
    switch (e) {
    #define ERROR_DEF_ENUM_CASE(name, value) \
        case name: return o << #name "[" << value << "]";
    ERROR_DEF_LIST(ERROR_DEF_ENUM_CASE);

    default: return o << "unknown[" << std::to_underlying(e) << "]";
    }
}
Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
jxh
  • 69,070
  • 8
  • 110
  • 193
  • I took the liberty to improve this solution so that it no longer requires an `#include` of an extra file. Feel free to revert the change if you don't like it. – Jan Schultke Aug 29 '23 at 08:49
  • @JanSchultke That's fine. Choosing to use a separate file is probably only desired if the list is really long. – jxh Aug 29 '23 at 15:24
8

This is a good way,

enum Rank { ACE = 1, DEUCE, TREY, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING };

Print it with an array of character arrays

const char* rank_txt[] = {"Ace", "Deuce", "Trey", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Jack", "Four", "King" } ;

Like this

std::cout << rank_txt[m_rank - 1]
MrPickles7
  • 654
  • 2
  • 10
  • 21
6

There has been a discussion here which might help: Is there a simple way to convert C++ enum to string?

UPDATE: Here#s a script for Lua which creates an operator<< for each named enum it encounters. This might need some work to make it work for the less simple cases [1]:

function make_enum_printers(s)
    for n,body in string.gmatch(s,'enum%s+([%w_]+)%s*(%b{})') do
    print('ostream& operator<<(ostream &o,'..n..' n) { switch(n){') 
    for k in string.gmatch(body,"([%w_]+)[^,]*") do
    print('  case '..k..': return o<<"'..k..'";')
    end
    print('  default: return o<<"(invalid value)"; }}')
    end
end

local f=io.open(arg[1],"r")
local s=f:read('*a')
make_enum_printers(s)

Given this input:

enum Errors
{ErrorA=0, ErrorB, ErrorC};

enum Sec {
    X=1,Y=X,foo_bar=X+1,Z
};

It produces:

ostream& operator<<(ostream &o,Errors n) { switch(n){
  case ErrorA: return o<<"ErrorA";
  case ErrorB: return o<<"ErrorB";
  case ErrorC: return o<<"ErrorC";
  default: return o<<"(invalid value)"; }}
ostream& operator<<(ostream &o,Sec n) { switch(n){
  case X: return o<<"X";
  case Y: return o<<"Y";
  case foo_bar: return o<<"foo_bar";
  case Z: return o<<"Z";
  default: return o<<"(invalid value)"; }}

So that's probably a start for you.

[1] enums in different or non-namespace scopes, enums with initializer expressions which contain a komma, etc.

Community
  • 1
  • 1
Nordic Mainframe
  • 28,058
  • 10
  • 66
  • 83
  • Isn't it a custom here to comment a '-1' to give the poster an opportunity to fix their answer? Just asking.. – Nordic Mainframe Jul 27 '10 at 12:25
  • 2
    I think the Boost PP solution below (from Philip) is better, because using external tools is very expensive maintainance wise. but no -1 because the answer is otherwise valid – Fabio Fracassi Jul 27 '10 at 12:33
  • 4
    The Boost PP *is also* a maintenance problem, because you need everyone to speak the Boost PP metalanguage, which is *terrible*, easy to break (giving usually unusable error messages) and only of limited usability (lua/python/perl can generate code from arbitrary external data). It adds boost to you dependency list, which may not even be allowed due to project policy. Also, it is invasive because it requires you to define your enums in a DSL. Your favorite source code tool or IDE might have trouble with that. And last but not least: you can't set a breakpoint in the expansion. – Nordic Mainframe Jul 27 '10 at 12:46
  • "external tools is very expensive" - if someone uses Emacs and this would be Elisp, not lua, you could have the generating snippet within the c++ file and then evaluate it and have it insert the generated code directly into the buffer. So external would be very relative. – BitTickler May 30 '22 at 13:21
6

I use a string array whenever I define an enum:

Profile.h

#pragma once

struct Profile
{
    enum Value
    {
        Profile1,
        Profile2,
    };

    struct StringValueImplementation
    {
        const wchar_t* operator[](const Profile::Value profile)
        {
            switch (profile)
            {
            case Profile::Profile1: return L"Profile1";
            case Profile::Profile2: return L"Profile2";
            default: ASSERT(false); return NULL;
            }
        }
    };

    static StringValueImplementation StringValue;
};

Profile.cpp

#include "Profile.h"

Profile::StringValueImplementation Profile::StringValue;
Mark Ingram
  • 71,849
  • 51
  • 176
  • 230
5

This solution doesn't require you to use any data structures or make a different file.

Basically, you define all your enum values in a #define, then use them in the operator <<. Very similar to @jxh's answer.

ideone link for final iteration: http://ideone.com/hQTKQp

Full code:

#include <iostream>

#define ERROR_VALUES ERROR_VALUE(NO_ERROR)\
ERROR_VALUE(FILE_NOT_FOUND)\
ERROR_VALUE(LABEL_UNINITIALISED)

enum class Error
{
#define ERROR_VALUE(NAME) NAME,
    ERROR_VALUES
#undef ERROR_VALUE
};

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
#define ERROR_VALUE(NAME) case Error::NAME: return os << "[" << errVal << "]" #NAME;
    ERROR_VALUES
#undef ERROR_VALUE
    default:
        // If the error value isn't found (shouldn't happen)
        return os << errVal;
    }
}

int main() {
    std::cout << "Error: " << Error::NO_ERROR << std::endl;
    std::cout << "Error: " << Error::FILE_NOT_FOUND << std::endl;
    std::cout << "Error: " << Error::LABEL_UNINITIALISED << std::endl;
    return 0;
}

Output:

Error: [0]NO_ERROR
Error: [1]FILE_NOT_FOUND
Error: [2]LABEL_UNINITIALISED

A nice thing about doing it this way is that you can also specify your own custom messages for each error if you think you need them:

#include <iostream>

#define ERROR_VALUES ERROR_VALUE(NO_ERROR, "Everything is fine")\
ERROR_VALUE(FILE_NOT_FOUND, "File is not found")\
ERROR_VALUE(LABEL_UNINITIALISED, "A component tried to the label before it was initialised")

enum class Error
{
#define ERROR_VALUE(NAME,DESCR) NAME,
    ERROR_VALUES
#undef ERROR_VALUE
};

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
#define ERROR_VALUE(NAME,DESCR) case Error::NAME: return os << "[" << errVal << "]" #NAME <<"; " << DESCR;
    ERROR_VALUES
#undef ERROR_VALUE
    default:
        return os << errVal;
    }
}

int main() {
    std::cout << "Error: " << Error::NO_ERROR << std::endl;
    std::cout << "Error: " << Error::FILE_NOT_FOUND << std::endl;
    std::cout << "Error: " << Error::LABEL_UNINITIALISED << std::endl;
    return 0;
}

Output:

Error: [0]NO_ERROR; Everything is fine
Error: [1]FILE_NOT_FOUND; File is not found
Error: [2]LABEL_UNINITIALISED; A component tried to the label before it was initialised

If you like making your error codes/descriptions very descriptive, you might not want them in production builds. Turning them off so only the value is printed is easy:

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
    #ifndef PRODUCTION_BUILD // Don't print out names in production builds
    #define ERROR_VALUE(NAME,DESCR) case Error::NAME: return os << "[" << errVal << "]" #NAME <<"; " << DESCR;
        ERROR_VALUES
    #undef ERROR_VALUE
    #endif
    default:
        return os << errVal;
    }
}

Output:

Error: 0
Error: 1
Error: 2

If this is the case, finding error number 525 would be a PITA. We can manually specify the numbers in the initial enum like this:

#define ERROR_VALUES ERROR_VALUE(NO_ERROR, 0, "Everything is fine")\
ERROR_VALUE(FILE_NOT_FOUND, 1, "File is not found")\
ERROR_VALUE(LABEL_UNINITIALISED, 2, "A component tried to the label before it was initialised")\
ERROR_VALUE(UKNOWN_ERROR, -1, "Uh oh")

enum class Error
{
#define ERROR_VALUE(NAME,VALUE,DESCR) NAME=VALUE,
    ERROR_VALUES
#undef ERROR_VALUE
};

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
#ifndef PRODUCTION_BUILD // Don't print out names in production builds
#define ERROR_VALUE(NAME,VALUE,DESCR) case Error::NAME: return os << "[" #VALUE  "]" #NAME <<"; " << DESCR;
    ERROR_VALUES
#undef ERROR_VALUE
#endif
    default:
        return os <<errVal;
    }
}
    ERROR_VALUES
#undef ERROR_VALUE
#endif
    default:
    {
        // If the error value isn't found (shouldn't happen)
        return os << static_cast<int>(err);
        break;
    }
    }
}

Output:

Error: [0]NO_ERROR; Everything is fine
Error: [1]FILE_NOT_FOUND; File is not found
Error: [2]LABEL_UNINITIALISED; A component tried to the label before it was initialised
Error: [-1]UKNOWN_ERROR; Uh oh
Xiao
  • 1,552
  • 2
  • 22
  • 20
4
#include <iostream>
using std::cout;
using std::endl;

enum TEnum
{ 
  EOne,
  ETwo,
  EThree,
  ELast
};

#define VAR_NAME_HELPER(name) #name
#define VAR_NAME(x) VAR_NAME_HELPER(x)

#define CHECK_STATE_STR(x) case(x):return VAR_NAME(x);

const char *State2Str(const TEnum state)
{
  switch(state)
  {
    CHECK_STATE_STR(EOne);
    CHECK_STATE_STR(ETwo);
    CHECK_STATE_STR(EThree);
    CHECK_STATE_STR(ELast);
    default:
      return "Invalid";
  }
}

int main()
{
  int myInt=12345;
  cout << VAR_NAME(EOne) " " << VAR_NAME(myInt) << endl;

  for(int i = -1; i < 5;   i)
    cout << i << " " << State2Str((TEnum)i) << endl;
  return 0;
}
2

You could use a stl map container....

typedef map<Errors, string> ErrorMap;

ErrorMap m;
m.insert(ErrorMap::value_type(ErrorA, "ErrorA"));
m.insert(ErrorMap::value_type(ErrorB, "ErrorB"));
m.insert(ErrorMap::value_type(ErrorC, "ErrorC"));

Errors error = ErrorA;

cout << m[error] << endl;
Adrian Regan
  • 2,240
  • 13
  • 11
  • 4
    How is this a map better that `switch(n) { case XXX: return "XXX"; ... }`? Which has O(1) lookup and does not need to be initialized? Or do enums change somehow during runtime? – Nordic Mainframe Jul 27 '10 at 11:37
  • I agree with @Luther Blissett on using switch statement (or a function pointer too) – KedarX Jul 27 '10 at 11:49
  • 1
    Well, he may want to output "This my dear friend Luther is Error A or "This my dear friend Adrian is Error B." Also, using map removes the dependancy on iostream signatures, such that he is free to use it elsewhere in the code with string concatenation for example, string x = "Hello" + m[ErrorA], etc. – Adrian Regan Jul 27 '10 at 11:49
  • I'm sure std::map contains a lot of if's and switches. I would read this as 'how can I do this without *having me* writing if's and switches' – Nordic Mainframe Jul 27 '10 at 12:24
  • I'm sure it does, but it certainly does not require you to write a script in Lua to solve the problem... – Adrian Regan Jul 27 '10 at 12:32
  • I haven't seen a solution until now, which 1) isn't intrusive (i.e. doesn't want me to define the enum using funny macros) and 2) doesn't require me to repeat the enum values. So far, a scripting solution allows me write the program as if automatic operator<< for enums was part of the language. – Nordic Mainframe Jul 27 '10 at 12:50
1

For this problem, I do a help function like this:

const char* name(Id id) {
    struct Entry {
        Id id;
        const char* name;
    };
    static const Entry entries[] = {
        { ErrorA, "ErrorA" },
        { ErrorB, "ErrorB" },
        { 0, 0 }
    }
    for (int it = 0; it < gui::SiCount; ++it) {
        if (entries[it].id == id) {
            return entries[it].name;
        }
    }
   return 0;
}

Linear search is usually more efficient than std::map for small collections like this.

Johan Kotlinski
  • 25,185
  • 9
  • 78
  • 101
0

How about this?

    enum class ErrorCodes : int{
          InvalidInput = 0
    };

    std::cout << ((int)error == 0 ? "InvalidInput" : "") << std::endl;

etc... I know this is a highly contrived example but I think it has application where applicable and needed and is certainly shorter than writing a script for it.

user633658
  • 2,463
  • 2
  • 18
  • 16
0

Use the preprocessor:

#define VISIT_ERROR(FIRST, MIDDLE, LAST) \
    FIRST(ErrorA) MIDDLE(ErrorB) /* MIDDLE(ErrorB2) */ LAST(ErrorC)

enum Errors
{
    #define ENUMFIRST_ERROR(E)  E=0,
    #define ENUMMIDDLE_ERROR(E) E,
    #define ENUMLAST_ERROR(E)   E
    VISIT_ERROR(ENUMFIRST_ERROR, ENUMMIDDLE_ERROR, ENUMLAST_ERROR)
    // you might undefine the 3 macros defined above
};

std::string toString(Error e)
{
    switch(e)
    {
    #define CASERETURN_ERROR(E)  case E: return #E;
    VISIT_ERROR(CASERETURN_ERROR, CASERETURN_ERROR, CASERETURN_ERROR)
    // you might undefine the above macro.
    // note that this will produce compile-time error for synonyms in enum;
    // handle those, if you have any, in a distinct macro

    default:
        throw my_favourite_exception();
    }
}

The advantage of this approach is that: - it's still simple to understand, yet - it allows for various visitations (not just string)

If you're willing to drop the first, craft yourself a FOREACH() macro, then #define ERROR_VALUES() (ErrorA, ErrorB, ErrorC) and write your visitors in terms of FOREACH(). Then try to pass a code review :).

lorro
  • 10,687
  • 23
  • 36