0

I am working on a senior project and having a question about how to best implement a lookup table for my program. There are several enum class files that contain enum classes and an operator<< overload to output them to std::strings. We are using boost property tree's to parse a JSON file and the parse cannot convert a string to an enum class by default. Since we need both the enum classes and the strings at some point in the program it made sense to implement an std::unordered_map. My issue though is where do I place the lookup table in terms of my files?

Currently our code has 4 enum class files which summarized are

namespace wiregen{
    enum class {/* values */}
    ostream& operator<<(ostream& os, enum){/*overload code to output enum as a string*/}
}

The enums need to be public as they are used by multiple classes. I currently have them defined and the operator<< overloads defined in the enum header files. My question though is should I place the lookup table in the enum header, make an enum implementation and move the lookup table there, or something else?

Jarod42
  • 203,559
  • 14
  • 181
  • 302
jleibman
  • 157
  • 1
  • 9

2 Answers2

1

Although this is slightly a design-based question, I would recommend placing the map in an anonymous namespace in order to hide it from the interface. This approach does not require an additional implementation file, since you can initialize constant unordered_map from a braced list.

// SomeHeader.h

enum class Color {                                                      
    Red,                                                                
    Blue,                                                               
    Green,                                                              
    Unknown,                                                            
};                                                                      

// Anonymous namespace to hide our implementation                                                                        
namespace {                                                             
std::unordered_map<std::string, Color> const stringToColorMap_ = {      
    { "Red",   Color::Red },                                            
    { "Blue",  Color::Blue },                                           
    { "Green", Color::Green },                                          
};                                                                      
}                                                                       

// Public interface                                                     
Color colorFromString(std::string const& s) {                           
    auto it = stringToColorMap_.find(s);                                
    if (it != stringToColorMap_.end()) {                                
        return it->second;                                              
    }                                                                   
    return Color::Unknown;                                              
}                                                                       

int main() {                                                            
    cout << static_cast<int>(colorFromString("Red")) << endl;           
    cout << static_cast<int>(colorFromString("Blue")) << endl;          
}                                                                       
Richard
  • 338
  • 2
  • 11
  • I seem to run into multiple definition conflicts when I use this approach. Is the function for colorFromString in the above case a free function? – jleibman Mar 14 '19 at 21:47
  • @jleibman Multiple definition usually occurs when you've `include`'d a file multiple times. See [this question](https://stackoverflow.com/questions/17904643/error-with-multiple-definitions-of-function/17905595#17905595) for more discussion about resolving that error (hint: read about 'include guards'). If you have a non-trivial build (e.g. multiple libraries depend on this), you may want to split the implementation of this function (and the anonymous namespace) to a `.cxx` file. – Richard Mar 14 '19 at 22:28
  • I have include guards in all headers currently, and before I changed the program to the above answer's structure, I had inline on operator<< overloads as well – jleibman Mar 14 '19 at 22:31
0

As a solution to this problem I found the best resolution was by wrapping the enums in a class and then making functions as part of that class to give the functionality I was looking for. I had to change them from scoped enumerations to normal enumerations but it now provides the behavior we needed.

A sample is given below

Base.hpp

namespace wiregen{
    class Base{
    public:
        enum baseEnum{
            /*removed for brevity*/
        };

        Base();
        constexpr Base(baseEnum b) : val(b){}
        bool operator==(Base b) const;
        bool operator!=(Base b) const;
        operator std::string() const;
        void operator = (const std::string &str);
    private:
        baseEnum val;
    };

    std::ostream& operator<<(std::ostream& os, const wiregen::Base& b);
}

Base.cpp

namespace{
    typedef boost::bimap<wiregen::Base::baseEnum, std::string>BaseTable;
    typedef BaseTable::value_type baseString;
    BaseTable m_base = boost::assign::list_of<baseString>
        /* removed for brevity */
}

namespace wiregen{
    Base::Base(){val = baseEnum::invalid;}
    bool Base::operator==(Base b) const{return val == b.val;} 
    bool Base::operator!=(Base b) const{return val != b.val;}
    Base::operator std::string() const{
        auto it = m_base.left.find(val);
        if(it != m_base.left.end()){
            return it->second;
        }
        return "invalid";
    }

    void Base::operator = (const std::string &str){
        auto it = m_base.right.find(boost::algorithm::to_lower_copy(str));
        if(it != m_base.right.end()){
            val = it->second;
            return;
        }
        std::cerr<<"Failed to assign Base: "<<str<<std::endl;
        return;
    }

    std::ostream& operator<<(std::ostream& os, const Base& b){
        os << (std::string)b;
        return os;
    }
}
jleibman
  • 157
  • 1
  • 9