1

I have some error codes that I would like represent as strings:

enum class ErrorCode
{
    OK,
    InvalidInput,
    BadAlloc,
    Other
};

I want to create an intuitive and simple way of getting strings that represent these errors. The simple solutions is:

std::string const ErrorCode2Str(ErrorCode errorCode)
{
  switch (errorCode)
  {
    case OK:
    return "OK";
    case InvalidInput:
    return "Invalid Input";
    case BadAlloc:
    return "Allocation Error";
    case Other:
    return "Other Error";
    default:
    throw Something;
  }
}

Is there a better way? Can I overload an ErrorCode to string cast somehow? Can I create a ErrorCode::str() function? Is there a standard solution to this problem?

quant
  • 21,507
  • 32
  • 115
  • 211
  • It's possible to hack it up with macros: http://stackoverflow.com/questions/201593/is-there-a-simple-script-to-convert-c-enum-to-string#201792 – Adam Oct 17 '13 at 04:04

3 Answers3

6

One possibility is a map:

class to_str { 
    std::unordered_map<ErrorCode, std::string> strings;
public:
    to_str() {
        strings[ErrorCode::OK] = "Ok";
        strings[ErrorCode::InvalidInput] = "Invalid Input";
        strings[ErrorCode::BadAlloc] = "Allocation Error";
        strings[ErrorCode::Other] = "Other";
    }

    std::string operator()(ErrorCode e) { 
        return strings[e];
    }
};

// ...
auto e = foo(some_input);
if (e != ErrorCode::OK)
   std::cerr << to_str()(e);

It's obviously not a huge difference, but I find it at least marginally more readable, and think it's probably a bit more maintainable in the long term.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • i think it would be better to have an interface whereby we can add more pairs to the map. am i write or is there some drawbacks to that? – Koushik Shetty Oct 17 '13 at 04:15
  • @Koushik: No, not generally -- you normally define strings for all the values in the enumeration, and there's no reason to add anything else. – Jerry Coffin Oct 17 '13 at 04:16
  • if there were more error codes to be added in the future then it will be better to rewrite this class ? – Koushik Shetty Oct 17 '13 at 04:18
  • @Koushik: If you change the enumeration, then yes, you'd modify the class that converts an ErrorCode to a string to suit. – Jerry Coffin Oct 17 '13 at 04:29
3

There is no prefect solution to this, and a lot of libraries out there do what you are currently doing.

But if you want a different way of doing it, you can turn the error into a class like so:

#include <iostream>
#include <string>
class Error
{
public:
    Error(int key, std::string message) : key(key), message(message){}
    int key;
    std::string message;
    operator int(){return key;}
    operator std::string(){ return message; }
    bool operator==(Error rValue){return this->key == rValue.key; } 
};

int main()
{
    Error e(0, "OK");

    int errorCode = e;
    std::string errorMessage = e;

    std::cout << errorCode << " " << errorMessage;
}
Caesar
  • 9,483
  • 8
  • 40
  • 66
  • This has most of the problems of a non-scoped enum, such as allowing implicit conversion from `Error` to `int`. It also places the entire burden of allocating error codes (ensuring they're non-overlapping, etc.) onto the client, instead of automating it like a enumeration does. – Jerry Coffin Oct 17 '13 at 06:58
  • @JerryCoffin You can't win all wars. For example, with your code, I could argue that it is not safe as someone can easily add another error the enum and forgot to add it to the class. Another issue with it is that you will need to allocate a map and fill it up every time you create `to_str` wasting memory and CPU. The beauty of creating the error as a class is that you can edit it your your satisfactions. If you don't like the example I gave above that is no problem as you can create another one that does. – Caesar Oct 17 '13 at 07:58
  • You may not be able to win all wars, but you seem to have lost nearly all of them. In the (unlikely) event that you care about the speed of printing out error messages, it's pretty trivial to make the `strings` map `static` without losing the other advantages--but I have a hard time imagining a situation where it would be worthwhile. – Jerry Coffin Oct 17 '13 at 09:00
  • @JerryCoffin I will agree that this is not the prefect solution but it is just as flawed as yours. Anyways, feel free to use which ever you like. – Caesar Oct 17 '13 at 09:04
1

Although many simple ways to do an enum-to-string or string-to-enum conversion exist, I woud like consider, here, a more generalized way.

Why doesn't C++ allow native contruct for it? There are mainly two reasons:

The first is technical: C++ doesn't have any reflection mechanism: compiled symbols simple cease to exist (and become just numbers). And since they don't exist, you cannot get them back.

The second is more a programming issue: enumerals are "shared" between the compiler and the programmer. String literals are shared between the progam and the end-user. That may be not a programmer and may not speak English (and we don't know what he speaks).

A general way to solve the problem is so to spkit it in two parts: one is at stream level, and the other at localization level.

What does it happen when you write std::cout << 42 ?

The operator<<(ostream&, int) implementation, in fact calls use_facet<num_put<char> >(cout.getloc()).do_put(int) which in turn use eventually the numpunct facet that define how to handle signs, decimal separator and digit group separators.

The standard way to handle enumeral output is so, by implementing an ostrea<<enumeral operator that gets a facet and calls on it a method to actually write that string.

Such a facet can them be implemented a number of times and made available for each supported language.

That's not easy and straightforward, but that's how C++ I/O is conceived.

Once you did all that, the idiomatic way to get a string is using a strngstream imbued with a local that supports all the enums and classes required facets.

Too complex? may be. But if you think this is too complicated, stop to teach std::cout << "Hello wrld" << std::endl; and write a more simple "output library".

Emilio Garavaglia
  • 20,229
  • 2
  • 46
  • 63