6
\$\begingroup\$

Question 1: functionxxx_as_string() is used below. How else could it be more elegantly named?

Question 2: Is the static char* array method adopted below the only solution? Best solution? Any suggestions?

Generally, I have this issue where my list of enums will be from 3 - say 50 items, mostly less than 20 items and they are fairly static.

   #include <iostream>

   enum thing_type
   {
      DTypeAnimal,
      DTypeMineral,
      DTypeVegetable,
      DTypeUnknown
   };

   class thing {
   public:
      thing(thing_type type) : type_(type) {}
      const thing_type get_state() const { return type_; }
      const char* get_state_as_string() const {
         static const char* ttype[] = { 
               "Animal",
               "Mineral",
               "Vegetable",
               "Unknown" 
         };
         return ttype[type_];   
      }

   private:
      thing_type type_;
   };


   int main() {
      thing th(DTypeMineral);
      std::cout << "this thing is a " << th.get_state_as_string() << std::endl;

      return 0;
   }

I am preferring to remove all the printing stuff from the class interface and use the operator<< overloading idea in 200_success answer like this:

#include <iostream>

enum thing_type
{
   DTypeUnknown,
   DTypeAnimal,
   DTypeMineral,
   DTypeVegetable
};

const char* type2string(thing_type ttype) {
   static const char* thtype[] = { 
      "Unknown",
      "Animal",
      "Mineral",
      "Vegetable"
   };
   return ttype < sizeof(thtype) ? thtype[ttype] : thtype[0];   
}

std::ostream& operator<<(std::ostream& os, const thing_type type) {
   os << type2string(type);
   return os;
}

class thing {
public:
   thing(thing_type type) : type_(type) {}
   const thing_type get_type() const { return type_; }

private:
   thing_type type_;
};

std::ostream& operator<<(std::ostream& os, const thing& th) {
   os << "This is a " << type2string(th.get_type());
   return os;
}

int main() {
   thing th(DTypeMineral);
   std::cout << th << std::endl;

   return 0;
}
\$\endgroup\$
  • \$\begingroup\$ In general, the values of an enum are not required to be unique, e.g. enum light { red, yellow, green, stop = 0, go = 2 };. That makes the name for a given value plural. \$\endgroup\$ – HABO Sep 12 '13 at 10:35
  • \$\begingroup\$ I find the way .net has it is awesome - xxxxToString? (But I'm biased :) \$\endgroup\$ – Mathieu Guindon Sep 12 '13 at 10:55
  • 1
    \$\begingroup\$ Also see: codereview.stackexchange.com/a/14315/507 \$\endgroup\$ – Martin York Sep 13 '13 at 2:58
4
\$\begingroup\$

Question 1: functionxxx_as_string() is used below. How else could it be more elegantly named?

How about:

std::ostream& operator<<(std::ostream& str, thing const& data);

Question 2: Is the static char* array method adopted below the only solution? Best solution? Any suggestions?

You will need a static char array (or equivalent (like a switch)) somewhere as there is no built in way to convert enum values (which are integers) to a string.

\$\endgroup\$
  • \$\begingroup\$ I feel a bit bad about changing my mind but only just seen byour answer. Agree overloading operator<< is better approach. See my update above. \$\endgroup\$ – arcomber Sep 13 '13 at 9:17
1
\$\begingroup\$

//I lost original answer I wanted to post on stackoverflow...

Question 1: functionxxx_as_string() is used below. How else could it be more elegantly named?

 const char *to_string(thing_type type);

It doesn't have to be class member.

Question 2: Is the static char* array method adopted below the only solution? Best solution? Any suggestions?

Not the only solution, may or may not be the best solution.

Potential problems - if your enum has "holes" enum Asdf{a = 1, b = 48, c}, array of names won't work.

You'll need to use array of pairs, std::map or plain old switch/case.

//warning: I haven't compiled the code, there might be typos.

array of pairs:

const char *tOstring(thing_type type){
    struct Data{
         thing_type type;
         const char *name;
    };

    static const Data data[] = {
         {thing, "thing"},
         ....
         {unusued_value, 0)
    };

    //if you use binary search here, it'll be faster
    for (const Data* cur = data[]; data->name; data++){
        if (cur->type == type)
            return cur->name;
    }
    return 0;
}

std::map:

typedef std::map<thing_type, std::string> NameMap;
NameMap nameMap;//somewhere
std::string toString(thing_type type){
     NameMap::const_iterator found = nameMap.find(type);
     if (found == nameMap.end())
           return std::string; //or throw exception
     return found.second;
}

You can make array of pairs static const, but std::map will need to be initialized somewhere. However, with std::map you can add/remove values at runtime. static const array of pairs and switch/case can't be changed at runtime.

Any suggestions?

You should probably use macros to initialize your array. Practical example (using switch/case):

const char* glErrorString(GLuint error){
    switch (error){
#define BRANCH(p) case(p): return #p; 
    BRANCH(GL_NO_ERROR)
    BRANCH(GL_INVALID_ENUM)
    BRANCH(GL_INVALID_VALUE)
    BRANCH(GL_INVALID_OPERATION)
    BRANCH(GL_STACK_OVERFLOW)
    BRANCH(GL_STACK_UNDERFLOW)
    BRANCH(GL_OUT_OF_MEMORY)
    BRANCH(GL_TABLE_TOO_LARGE   )
    default:
        return "Unknown error\n";
#undef  BRANCH
    };
}
\$\endgroup\$
1
\$\begingroup\$

You should use one of the preprocessor macro-based solutions proposed in StackOverflow Question 147267. For example, here is a solution using X() macros. I've also rewritten your get_state_as_string() as a stringifier for iostream.

#include <iostream>

#define X_THINGS \
  X(DTypeAnimal,    "Animal") \
  X(DTypeMineral,   "Mineral") \
  X(DTypeVegetable, "Vegetable") \
  X(DTypeUnknown,   "Unknown")

typedef enum {
#   define X(Enum, String) Enum,
    X_THINGS
#   undef X
} thing_type;

class Thing {
  public:
    Thing(thing_type type) : type_(type) {}
    const thing_type get_state() const { return type_; }
    friend std::ostream &operator<<(std::ostream&, const Thing&);

  private:
    thing_type type_;
};

std::ostream &operator<<(std::ostream &os, const Thing &t) {
    switch (t.get_state()) {
#   define X(Enum, String) \
      case Enum: os << String; break;
      X_THINGS
#   undef X
    }
    return os;
}

int main() {
   Thing th(DTypeMineral);
   std::cout << "this thing is a " << th << std::endl;

   return 0;
}
\$\endgroup\$
  • \$\begingroup\$ I'd paste at least one of the examples here to make this answer more valuable. \$\endgroup\$ – Jamal Sep 12 '13 at 12:18
  • \$\begingroup\$ Yes I don't like all the as_string functions. I prefer overloading operator<< - and taking the printing stuff out of the class. \$\endgroup\$ – arcomber Sep 12 '13 at 17:49
  • 4
    \$\begingroup\$ Since this is C++ proposing a C solution is probably not a good idea. The same affective can be done much better using language features in C++. \$\endgroup\$ – Martin York Sep 13 '13 at 3:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.