views:

219

answers:

8

Suppose I have a list of #defines in a header file for an external library. These #defines represent error codes returned from functions. I want to write a conversion function that can take as an input an error code and return as an output a string literal representing the actual #define name.

As an example, if I have

#define NO_ERROR 0
#define ONE_KIND_OF_ERROR 1
#define ANOTHER_KIND_OF_ERROR 2

I would like a function to be able to called like

int errorCode = doSomeLibraryFunction();
if (errorCode)
    writeToLog(convertToString(errorCode));

And have convertToString() be able to auto-convert that error code without being a giant switch-case looking like

const char* convertToString(int errorCode)
{
    switch (errorCode)
    {
        case NO_ERROR:
           return "NO_ERROR";
        case ONE_KIND_OF_ERROR:
           return "ONE_KIND_OF_ERROR";
        ...
     ...
...

I have a feeling that if this is possible, it would be possible using templates and metaprogramming, but that would only work the error codes were actually a type and not a bunch of processor macros.

Thanks

+4  A: 

You are correct. There's no way to recover preprocessor-defined identifiers at runtime (unless you can read the source, but that's cheating). You would be better off creating a constant array of the names and indexing it with the error code (with proper boundary checks of course) - it should be quite easy to write a script to generate it.

Max Shawabkeh
Reason for downvote?
Max Shawabkeh
But is there any way that the constant array can be generated at compile time without kicking out to some custom pre-build step?
brandonC
There's no way to generate it at compile time using standard C/C++. The closest you can get is something like Michael Morzek's `STR(...)` answer.
Max Shawabkeh
+13  A: 

I normally do it the giant switch case way, although I make it somewhat easier with:

#define STR(code) case code: return #code
switch (errorCode)
{
    STR(NO_ERROR);
    STR(ONE_KIND_OF_ERROR);
}

This is a good question, I'm interested to see what better ways people have

Michael Mrozek
+1 I've never thought about this method before.
Mark B
A: 

#define FOO 1 is handled by the preprocessor as a simple text replacement. If you want to preserve those definitions, you need the giant switch.

Brian Roach
+3  A: 

Another way to do it that's popular in generated code is:

#define NO_ERROR 0
#define ONE_KIND_OF_ERROR 1
#define ANOTHER_KIND_OF_ERROR 2
static const char* const error_names[] = {"NO_ERROR", "ONE_KIND_OF_ERROR", "ANOTHER_KIND_OF_ERROR"};

const char* convertToString(int errorCode) {return error_names[errorCode];}

I prefer the switch case way I already mentioned, but depending on how your code is structured it might be easier as part of your build process to generate that array automatically

Michael Mrozek
That's certainly a cool way, and a lot easier on the typing than the switch-case.I just wish there was a way to generate the array at compile time.
brandonC
+2  A: 

take a look at boost preprocessor. You could create list/array/sequence of code pairs:

#define codes ((1,"code1"))((...))

#define code1 1
...

// then use preprocessor FOR_EACH to generate error handlers

relevant link:

http://www.boost.org/doc/libs/1_41_0/libs/preprocessor/doc/ref/seq_for_each.html

http://www.boost.org/doc/libs/1_41_0/libs/preprocessor/doc/ref/tuple_elem.html

aaa
Here's a full example of a similar type of problem implemented using Boost Preprocessor: http://stackoverflow.com/questions/2576868/c-c-enums-detect-when-multiple-items-map-to-same-value/2577102#2577102 (it probably wouldn't take _too_ much work to modify that) </shameless-answer-promotion>
James McNellis
@James thanks, I was too lazy to provide more complete listing.
aaa
That's a really cool solution, and certainly something close to what I was looking for. I don't think it would work in my particular situation, not controlling the .h file and all, but certainly something I'd love to find a use for
brandonC
@brandonC in this case, you might want to check out boost wave which is sort of programmable preprocessor (C++ header, no external tools). I am just now messing with it so I cannot really give you more info about it. as far as boost preprocessor for me greatest use was factory patterns over template instantiation.
aaa
+1  A: 

One possibility here is to write a little program that parses the .h file that contains the #defines, and emits the corresponding source code for the convertToString() function. Then you can have that program automatically run as part of your build process whenever the .h file is changed. It's a little more work up front, but once it's implemented you'll never again need to manually update your int<->string conversion function.

This is especially useful if you want to support code in multiple languages that uses the same constants. For example, in one of my applications, changing my #defines header file causes the corresponding C++, Python, and Java files to be auto-regenerated. This makes project maintenance much easier and less error-prone.

Jeremy Friesner
I would agree with that. My hope, however, was to be able to do that generation entirely within the C/C++ preprocessor. My greatest reservation with writing a custom pre-build tool to do code generation is that I would spend more time writing and maintaining the code-gen tool than actually getting use out of, especially since , as the `#define`s are provided as part of an external library, I would hope they'd be very unlikely to change.
brandonC
+1  A: 

If you definitely want to keep the #define's I would go with Michael's elegant #define STR(code) answer. But defines are more C than C++, and the big downside to defines is you can't put them in a namespace. They will pollute the global namespace of any program you include them in. If it's in your power to change it, I would recommend using an anonymous enum instead:

enum{ NO_ERROR ONE_KIND_OF_ERROR ANOTHER_KIND_OF_ERROR }

This is exactly the same as the #defines you have, and you can put it in a namespace. And now you can use Michael's other answer involving the static const char* const error_names array yo do what you had originally asked.

Goose Bumper
It's a good answer, and certainly what I'd do if I actually controlled the header files, but unfortunately these are provided as part of an external library, so changing the #defines into enums, while preferable, isn't really an option in this case :(
brandonC
@Goose, I have been doing it this way all along. Got a new perspective with switch though.
rocknroll
@brandonC: I thought that was the case. Oh well.
Goose Bumper
+1  A: 

You can actually have it both ways, ie having two functions that translate from code to error back and forth.

The first thing of course is that #define should not be used for constants, an enum would probably be best, however an enum cannot be extended, which requires that all your errors be defined in the same place (ouch, thank you so much for dependencies...)

You can do it another may though, using namespaces to isolate the symbols, and preprocessing to handle the whole generation. For the lookup part, we'll use a Bimap.

First we need to define a Handler class (not necessary to inline all that, but it's easier for examples)

#include <boost/bimap.hpp>
#include <boost/optional.hpp>

namespace error
{

  class Handler
  {
  public:
    typedef boost::optional<int> return_code;
    typedef boost::optional<std::string> return_description;

    static bool Register(int code, const char* description)
    {
      typedef error_map::value_type value_type;
      bool result = MMap().insert(value_type(code,description)).second;

      // assert(result && description)
      return result;
    }

    static return_code GetCode(std::string const& desc)
    {
      error_map::map_by<description>::const_iterator it
          = MMap().by<description>().find(desc);
      if (it != MMap().by<description>().end()) return it->second;
      else return return_code();
    }

    static return_description GetDescription(int c)
    {
      error_map::map_by<code>::const_iterator it
          = MMap().by<code>().find(c);
      if (it != MMap().by<code>().end()) return it->second;
      else return return_description();
    }

    typedef std::vector< std::pair<int,std::string> > errors_t;
    static errors_t GetAll()
    {
      errors_t result;
      std::for_each(MMap().left.begin(), MMap().left.end(),
                    result.push_back(boost::lambda::_1));
      return result;
    }

  private:
    struct code {};
    struct description {};

    typedef boost::bimap<
      boost::tagged<int, code>,
      boost::tagged<std::string, description>
    > error_map;

    static error_map& Map() { static error_map MMap; return MMap; }
  };

  // Short-Hand
  boost::optional<int> GetCode(std::string const& d)
  {
    return Handler::GetCode(d);
  }

  boost::optional<std::string> GetDescription(int c)
  { 
    return Handler::GetDescription(c);
  }
} // namespace error

Then we just need to provide some syntactic sugar:

#define DEFINE_NEW_ERROR(Code_, Description_)            \
  const int Description_ = Code_;                        \
  namespace error {                                      \
    const bool Description##_Registered =                \
      ::error::Handler::Register(Code_, #Description_);  \
  }

We could be a bit more violent in case of a registration of an unknown error (assert for example).

And then we can always wrap that macro into one that can define multiple symbols in one go... but that's left as an exercise.

Usage:

// someErrors.hpp
#include "error/handler.hpp"

DEFINE_NEW_ERROR(1, AnError)
DEFINE_NEW_ERROR(2, AnotherError)

// someFile.cpp
#include <iostream>
#include "error/handler.hpp"

int main(int argc, char* argv[])
{
  int code = 6;
  boost::optional<std::string> desc = error::GetDescription(code);

  if (desc)
  {
    std::cout << "Code " << code << " is mapped to <" << *desc << ">" << std::endl;
  }
  else
  {
    std::cout << "Code " << code << " is unknown, here is the list:\n";

    ::error::Handler::errors_t errors = ::Error::Handler::GetAll();

    std::for_each(errors.begin(), errors.end(), std::cout << "  " << _1);

    std::cout << std::endl;
  }
}

Disclaimer: I am not too sure about the lambda syntax, but it did simplified the writing.

Matthieu M.
Again, an awesome solution, if I was writting a library and controlled the error codes coming out. Unfortunately, someone else has already provided me with the library that chooses to return error codes via #define'd ints. Oh well, I figured what I wanted was probably not possible. Thanks for the informative answer, however.
brandonC