Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

I have the following redundant-feeling design to convert between enums and strings regarding a class that stores enums. The approach doesn't scale if there are more enums and in any event, less-redundant code is also better.

Questions

  1. If there will be more enums, would it be possible to avoid defining two explicit conversion functions per enum type and device a system where the caller sees just one (i.e. convert) or two different function names (i.e. convertto/convertfrom for all the enums, not just per enum type)? Perhaps using some kind deduction magic with auto and decltype? It looks like ambiguity sets in since only the return value can be used to separate the different functions overloads (even if done with function templates).

  2. Is the following design of separating the conversion functions and putting them to an anonymous namespace good design (I've thought about putting the conversion functions to a file, say conversions.incl and including it)?

The idea would be make the multiple (i.e. more enums than the one presented here) conversions as implicit as possible

The conversions would be used like this:

random.cpp

string token_string = "none"; //In reality this will be externally, user, generated.
some_class_instance->set_type(enum_conversion(token_string));
token_string = enum_conversion(some_class_instance->get_type());

And to present one enum and related conversions (but there could be more):

some_class.h

class some_class
{
    public:
         enum class enum_type
         {
             none   = 0,
             type1  = 1,
             type2  = 2
         }

     void set_type(enum_type);
     enum_type get_type() const;

   private:
       enum_type type_;
};

namespace
{
    std::array<std::pair<std::string, some_class::enume_type>, 3> type_map;

    bool initialize_map()
    {
       type_map[0] = std::make_pair("none", some_class::enum_type::none);
       type_map[1] = std::make_pair("type1", some_class::enum_type::type1);
       type_map[2] = std::make_pair("type2", some_class::enum_type::type2);
    }

    bool initialization_result = initialize_map();

    some_class::enum_type enum_conversion(std::string const& enum_type)
    {
        for(auto val: type_map)
        {
            if(val.first == enum_type)
            {
                return val.second;
            }
        }

        return type_map[0].second;
    }

    std::string enum_conversion(some_class::enum_type enum_type)
    {
        for(auto val: type_map)
        {
            if(val.second == enum_type)
            {
                return val.first;
            }
        }

        return type_parameter_map[0].first;
    }
}
share|improve this question
add comment

3 Answers 3

up vote 7 down vote accepted

I would use some template logic to achieve the affect in a more scalable way:

#include <iostream>
#include <sstream>
#include <string>
#include <algorithm>

// This is the type that will hold all the strings.
// Each enumerate type will declare its own specialization.
// Any enum that does not have a specialization will generate a compiler error
// indicating that there is no definition of this variable (as there should be
// be no definition of a generic version).
template<typename T>
struct enumStrings
{
    static char const* data[];
};

// This is a utility type.
// Creted automatically. Should not be used directly.
template<typename T>
struct enumRefHolder
{
    T& enumVal;
    enumRefHolder(T& enumVal): enumVal(enumVal) {}
};
template<typename T>
struct enumConstRefHolder
{
    T const& enumVal;
    enumConstRefHolder(T const& enumVal): enumVal(enumVal) {}
};

// The next too functions do the actual work of reading/writtin an
// enum as a string.
template<typename T>
std::ostream& operator<<(std::ostream& str, enumConstRefHolder<T> const& data)
{
   return str << enumStrings<T>::data[data.enumVal];
}

template<typename T>
std::istream& operator>>(std::istream& str, enumRefHolder<T> const& data)
{
    std::string value;
    str >> value;

    // These two can be made easier to read in C++11
    // using std::begin() and std::end()
    //  
    static auto begin  = std::begin(enumStrings<T>::data);
    static auto end    = std::end(enumStrings<T>::data);

    auto find   = std::find(begin, end, value);
    if (find != end)
    {   
        data.enumVal = static_cast<T>(std::distance(begin, find));
    }   
    return str;
}


// This is the public interface:
// use the ability of function to deuce their template type without
// being explicitly told to create the correct type of enumRefHolder<T>
template<typename T>
enumConstRefHolder<T>  enumToString(T const& e) {return enumConstRefHolder<T>(e);}

template<typename T>
enumRefHolder<T>       enumFromString(T& e)     {return enumRefHolder<T>(e);}

Then you can use it like this:

// Define Enum Like this
enum X {Hi, Lo};
// Then you just need to define their string values.
template<> char const* enumStrings<X>::data[] = {"Hi", "Lo"};

int main()
{
    X   a=Hi;

    std::cout << enumToString(a) << "\n";

    std::stringstream line("Lo");
    line >> enumFromString(a);

    std::cout << "A: " << a << " : " << enumToString(a) << "\n";
}
share|improve this answer
    
I feel this design is far superior. I tried something like this, but run into problems with the specializations (hence my comment on overloading with specializations). Then I went into the second best alternative I could make to compile. The simple arrays feel like being enough (and perhaps the fastest) since they will be quite short, around ten items maximum. But anyway, thanks for the code and heads up, this was a good learning experience! –  Veksi Aug 4 '12 at 7:48
    
A further question, if I still may, how would one define the conversions without using streams? E.g. template<typename T> T convertTo(std::string const& token) { std::stringstream line(token); T a; line >> enumFromString(a); return a; } but this doesn't feel the most straightforward solution. Also, for some reason, I can't seem to find a way to do this to the other direction, to produce a string from enum. } –  Veksi Aug 4 '12 at 12:27
    
Hmm... Moreover, now when I'm trying this more, it looks like the ostream conversion doesn't compile. My VS 2012 RC fails with error messages "Error 1 error C2440: '<function-style-cast>' : cannot convert from 'const some_namespace::some_class::X' to 'some_namespace::`anonymous-namespace'::enumConstRefHolder<T>'" and error C2677: binary '[' : no global operator found which takes type 'const some_namespace::some_class::X' (or there is no acceptable conversion)" And I'm too rookie to fix the error message myself, I gather. Can I still lend your hand for a moment..? :) –  Veksi Aug 4 '12 at 14:13
    
I think I've found the crux of the matter: my enums are strongly typed, so they can't be used to index the arrays as-is, but with a cast like so "return str << enumStrings<T>::data[static_cast<int>(data.enumValue_)];" then also the constructor of EnumConstRefHolder needs to take the parameter by constant reference. It looks like the strongly typed enums can be cast to integer and they work without their underlying type specified. Though, it probably is better to specify one. –  Veksi Aug 4 '12 at 16:06
    
To still add comments (if someone cares to read them this far), the default underlying type is int, but it can be changed and hence it's safer to "interrogate" it during compilation. The cast can be done with std::underlying_type like this "return str << enumStrings<T>::data[static_cast<std::underlying_type<T>::type>(data.enumValue)"‌​. –  Veksi Aug 4 '12 at 16:24
show 4 more comments

In most cases, the requirement of converting between c++ enumeration and string representation arises from interfacing to another program that does not understand your enumeration declarations. Often this other program will be a SQL database.

Hence I would go for a code generator to ensure consistency over the whole system.

The code generator could walk the whole database catalog and create an enumeration c-header and corresponding string arrays for everything in the database that could potentially carry enumeration semantics. Writing such a generator is less then a days work.

Note that this approach does not only free you from writing the string part. It also frees you from writing the enumeration definition altogether.

share|improve this answer
    
This is the approach I use for embedded firmware programming. I use the most capable and expressive language (say C#) and generate headers for all the less sophisticated languages (say C or C++), directly from the C# code, which is annotated with Attributes. –  Mark Lakata Jun 20 at 22:20
add comment

A good start.
But you need to templatize your functions and the type_map by the enum to make the design extensible. But that is a simple change so I will not focus on that.

About the only other thing is that you do a search when doing a conversion in either direction. By choosing the appropriate container to hold the information you can do a quick lookup in one direction (though not in both without some fancier than normal containers).

Otherwise I quite like it.

Its not the style I would have chosen. See my alternative below.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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