Search code examples
c++boostenumsboost-propertytree

Generic enum translator for boost::property_tree


I'm loading/saving a set of parameters from/to a file using boost::property_tree. Many of those parameters are enumerations (different types). So I need a way to get enums from a boost::property_tree (i.e., converting a string to enum), and viceversa. For example

const Enum_1 position = params.get<Enum_1>("Test.position");

I've checked out this answer, which involves creating a translator for each enumeration. As I have several dozens of enumerations, it looks like a bit overwhelming.

Is there any more generic way to do it when many enums are involved?

PS: I'm posting my current solution in an answer since I haven't been able to find something easier/simpler. I'll be glad to hear better options.


Solution

  • My current solution consists on a templated translator that relies on a boost::bimap to ease the std::string/enum conversion.

    // Generic translator for enums
    template<typename T>
    struct EnumTranslator {
      typedef std::string internal_type;
      typedef T external_type;
      typedef boost::bimap<internal_type, external_type> map_type;
    
      boost::optional<external_type> get_value(const internal_type& str) {
        // If needed, 'str' can be transformed here so look-up is case insensitive
        const auto it = s_map.left.find(str);
        if (it == s_map.left.end()) return boost::optional<external_type>(boost::none);
        return boost::optional<external_type>(it->get_right());
      }
    
      boost::optional<internal_type> put_value(const external_type& value) {
        const auto it = s_map.right.find(value);
        if (it == s_map.right.end()) return boost::optional<internal_type>(boost::none);
        return boost::optional<internal_type>(it->get_left());
      }
    
    private:
      static const map_type s_map;
    };
    

    Such dictionaries are then defined for each enum:

    // Dictionaries for string<-->enum conversion
    typedef EnumTranslator<Enum_1> Enum_1_Translator;
    const Enum_1_Translator::map_type Enum_1_Translator::s_map =
      boost::assign::list_of<Enum_1_Translator::map_type::relation>
      ("first", Enum_1::first)
      ("second", Enum_1::second)
      ("third", Enum_1::third);
    
    typedef EnumTranslator<Enum_2> Enum_2_Translator;
    const Enum_2_Translator::map_type Enum_2_Translator::s_map =
      boost::assign::list_of<Enum_2_Translator::map_type::relation>
      ("foo", Enum_2::foo)
      ("bar", Enum_2::bar)
      ("foobar", Enum_2::foobar);
    

    Finally, the translators must be registered so they can be used by boost::property_tree.

    // Register translators
    namespace boost {
      namespace property_tree {
        template<typename Ch, typename Traits, typename Alloc>
        struct translator_between<std::basic_string<Ch, Traits, Alloc>, Enum_1> {
          typedef Enum_1_Translator type;
        };
    
        template<typename Ch, typename Traits, typename Alloc>
        struct translator_between<std::basic_string<Ch, Traits, Alloc>, Enum_2> {
          typedef Enum_2_Translator type;
        };
      }
    }
    

    Final example of use (params is a boost::property_tree::ptree):

    const Enum_1 position = params.get<Enum_1>("Test.position");
    const Enum_2 foo_or_bar = params.get<Enum_2>("Test.foo_or_bar");
    

    Maybe someone would prefer to add some macros to reduce the code cluttering, for example:

    #define DECLARE_ENUM_TRANSLATOR(E) \
      typedef EnumTranslator<E> E##EnumTranslator; \
      const E##EnumTranslator::map_type E##EnumTranslator::s_map = \
        boost::assign::list_of<E##EnumTranslator::map_type::relation>
    
    #define REGISTER_ENUM_TRANSLATOR(E) \
      namespace boost { namespace property_tree { \
      template<typename Ch, typename Traits, typename Alloc> \
      struct translator_between<std::basic_string<Ch, Traits, Alloc>, E> { \
        typedef E##EnumTranslator type; \
      }; } }
    

    In this way, new enums can be registered by:

    DECLARE_ENUM_TRANSLATOR(Enum_1)
      ("first", Enum_1::first)
      ("second", Enum_1::second)
      ("third", Enum_1::third);
    REGISTER_ENUM_TRANSLATOR(Enum_1);
    
    DECLARE_ENUM_TRANSLATOR(Enum_2)
      ("foo", Enum_2::foo)
      ("bar", Enum_2::bar)
      ("foobar", Enum_2::foobar);
    REGISTER_ENUM_TRANSLATOR(Enum_2);
    

    Note: these macros are not compatible with enums within a namespace or class, due to the double colons (a_namespace::the_enum). As a workaround, a typedef can be done to rename the enumeration, or just do not use the macros in these cases ;).