tags:

views:

222

answers:

7

If i have an enum like this

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

Then i want to print out to console

Errors anError = ErrorA;
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?

+3  A: 

Use an array or vector of strings with matching values:

char *ErrorTypes[] =
{
    "errorA",
    "errorB",
    "errorC"
};

cout << ErrorTypes[anError];

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];
Igor Oks
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)
Luther Blissett
@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 Oks
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?
Luther Blissett
@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 Oks
+2  A: 

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
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?
Luther Blissett
I agree with @Luther Blissett on using switch statement (or a function pointer too)
Kedar
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
Also the question asked how to do it without an if or switch...
Adrian Regan
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'
Luther Blissett
I'm sure it does, but it certainly does not require you to write a script in Lua to solve the problem...
Adrian Regan
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.
Luther Blissett
+3  A: 

There has been a discussion here which might help: http://stackoverflow.com/questions/201593/is-there-a-simple-script-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.

Luther Blissett
Isn't it a custom here to comment a '-1' to give the poster an opportunity to fix their answer? Just asking..
Luther Blissett
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
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.
Luther Blissett
s/invasive/intrusive/g
Luther Blissett
+3  A: 

Using map:

#include <iostream>
#include <map>
#include <string>

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

std::ostream& operator<<(std::ostream& out, const Errors value){
    static std::map<Errors, std::string> strings;
    if (strings.size() == 0){
#define INSERT_ELEMENT(p) strings[p] = #p
        INSERT_ELEMENT(ErrorA);     
        INSERT_ELEMENT(ErrorB);     
        INSERT_ELEMENT(ErrorC);             
#undef INSERT_ELEMENT
    }   

    return out << strings[value];
}

int main(int argc, char** argv){
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
    return 0;   
}

Using array of structures with linear search:

#include <iostream>
#include <string>

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;
        const char* 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;
}

int main(int argc, char** argv){
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
    return 0;   
}

Using switch/case:

#include <iostream>
#include <string>

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

std::ostream& operator<<(std::ostream& out, const Errors value){
    const char* s = 0;
#define PROCESS_VAL(p) case(p): s = #p; break;
    switch(value){
        PROCESS_VAL(ErrorA);     
        PROCESS_VAL(ErrorB);     
        PROCESS_VAL(ErrorC);
    }
#undef PROCESS_VAL

    return out << s;
}

int main(int argc, char** argv){
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
    return 0;   
}
SigTerm
-1. Just do a switch-case instead of using a hash-map. Increased complexity is not a good thing.
Simon
@Simon: Instead of commenting on my code why don't you simply provide better answer?
SigTerm
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
+1  A: 

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.

kotlinski
+4  A: 

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)          \
  };                                                            \
  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;
}
Philipp
+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
@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
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
+1  A: 

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