用于boost :: property_tree的通用枚举转换器

时间:2018-09-06 14:55:40

标签: c++ boost enums boost-propertytree

我正在使用boost::property_tree从文件中加载/保存一组参数。这些参数中有许多是枚举(不同类型)。因此,我需要一种从boost::property_tree获取枚举的方法(即,将字符串转换为枚举),反之亦然。例如

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

我已经签出this answer,其中涉及为每个枚举创建一个翻译器。由于我有几十个枚举,因此看起来有点不知所措。

当涉及到许多枚举时,还有其他通用的方法吗?

PS:由于无法找到更简单/简单的方法,因此我将当前的解决方案发布在答案中。我很高兴听到更好的选择。

2 个答案:

答案 0 :(得分:1)

我当前的解决方案包括依赖boost::bimap来简化std::string /枚举转换的模板转换器。

// 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;
};

然后为每个枚举定义此类字典:

// 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);

最后,必须注册翻译器,以便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;
    };
  }
}

最终使用示例(paramsboost::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");

也许有人希望添加一些宏来减少代码混乱,例如:

#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; \
  }; } }

通过这种方式,新的枚举可以通过以下方式注册:

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);

注意:由于双冒号(a_namespace::the_enum),这些宏与命名空间或类中的枚举不兼容。解决方法是,可以使用typedef来重命名枚举,或者在这种情况下不要使用宏;)。

答案 1 :(得分:1)

看看标题,自定义的好处是:

namespace boost { namespace property_tree
{

  template <typename Ch, typename Traits, typename E, typename Enabler = void>
  struct customize_stream
  {
    static void insert(std::basic_ostream<Ch, Traits>& s, const E& e) {
        s << e;
    }
    static void extract(std::basic_istream<Ch, Traits>& s, E& e) {
        s >> e;
        if(!s.eof()) {
            s >> std::ws;
        }
    }
  };

它具有一个启动器字段。

namespace boost { namespace property_tree {
  template <typename Ch, typename Traits, typename E>
  struct customize_stream<Ch, Traits, E,
    std::enable_if_t< /* some test */ >
  >
  {
    static void insert(std::basic_ostream<Ch, Traits>& s, const E& e) {
      // your code
    }
    static void extract(std::basic_istream<Ch, Traits>& s, E& e) {
      // your code
    }
  };

您可以在其中将任何代码放入// your code中,并将任何测试放入/* some test */中。

其余部分用于(a)将bob::a"a"关联,并且(b)将其与上面的内容联系起来。

我喜欢在bob的命名空间中进行这些关联,然后通过ADL查找它们。

创建一个template<class T> struct tag_t {}。如果将tag_t<foo>传递给函数,则ADL将在tag_t的命名空间和foo的命名空间中找到函数。

创建一个函数,该函数获取从枚举值到字符串(以及向后)的映射。假设您的映射是:

std::vector< std::pair< E, std::string > >

,您只需进行线性搜索。然后:

namespace string_mapping {
  template<class Enum>
  using mapping = std::vector<std::pair< Enum, std::string > >;
}
namespace some_ns {
  enum bob { a, b, c };
  string_mapping::mapping<bob> const& get_string_mapping( tag_t<bob> ) {
    static string_mapping::mapping<bob> retval = {
      {bob::a, "a"},
      {bob::b, "b"},
      {bob::c, "c"},
    };
    return retval;
  }
}

通过执行T=bob,我们可以在类型为get_string_mapping( tag_t<T>{} )的任何地方找到此映射。

使用类似can_apply的东西来检测是否可以找到get_string_mapping( tag_t<T>{} ),使用它来使您的自定义customize_stream能够使用get_string_mapping来往/从中保存数据流。

现在我们要做的就是减轻编写get_string_mapping的痛苦。

#define MAP_ENUM_TO_STRING( ENUM ) \
  string_mapping::mapping<ENUM> const& get_string_mapping( tag_t<ENUM> ) { \
    static string_mapping::mapping<ENUM> retval =

#define END_ENUM_TO_STRING ; return retval; }

使用:

 MAP_ENUM_TO_STRING( bob )
    {
      {bob::a, "a"},
      {bob::b, "b"},
      {bob::c, "c"},
    }
 END_ENUM_TO_STRING

bob的命名空间中。

如果您想要更高级的东西(呜呼,排序列表或无序地图),可以在get_string_mapping内甚至通过调用get_efficient_string_mapping并执行一个的get_string_mapping轻松完成平面数据的重新处理。

此方法的最大优点是我们不必在全局名称空间中执行此操作;我们可以自然地将其放在枚举下或枚举的命名空间中。