Универсальный переводчик enum для boost :: property_tree - PullRequest
0 голосов
/ 06 сентября 2018

Я загружаю / сохраняю набор параметров из / в файл, используя boost::property_tree. Многие из этих параметров являются перечислениями (разных типов). Поэтому мне нужен способ получить перечисления из boost::property_tree (то есть преобразование строки в перечисление) и наоборот. Например

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

Я проверил этот ответ , который включает в себя создание переводчика для каждого перечисления. Поскольку у меня есть несколько десятков перечислений, это выглядит немного подавляющим.

Есть ли более общий способ сделать это, когда задействовано много перечислений?

PS: я публикую свое текущее решение в ответ, так как я не смог найти что-то более легкое / простое. Я буду рад услышать лучшие варианты.

Ответы [ 2 ]

0 голосов
/ 06 сентября 2018

Глядя на заголовок, хорошая точка настройки:

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

имеет поле Enabler.

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 */.

Оставшаяся часть состоит в том, чтобы (а) связать bob::a с "a" и (б) связать это с вышеприведенным.

Мне нравится делать эти ассоциации в пространстве имен bob, а затем находить их через ADL.

Создать template<class T> struct tag_t {}. Если вы передадите tag_t<foo> функции, ADL найдет функции как в пространстве имен tag_t, так и в пространстве имен foo.

Создать функцию, которая получает отображение от значения enum на строку (и обратно). Предположим, что ваше отображение:

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.

Если вы хотите что-то более изощренное (ooo, отсортированные списки или неупорядоченные карты), это можно легко сделать в get_string_mapping или даже с помощью get_efficient_string_mapping, который вызывает get_string_mapping и выполняет однократную повторную обработку квартиры данные.

Большим преимуществом этого является то, что нам не нужно делать это в глобальном пространстве имен; мы можем поместить это естественно прямо в перечисление или в пространство имен перечисления.

0 голосов
/ 06 сентября 2018

Мое текущее решение состоит из шаблонного переводчика, который использует boost::bimap для упрощения преобразования std::string / enum.

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

Последний пример использования (params - это 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");

Может быть, кто-то предпочтет добавить несколько макросов для уменьшения помех в коде, например:

#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, чтобы переименовать перечисление, или просто не использовать макросы в этих случаях;).

...