boost::property_tree 的通用枚举转换器
Generic enum translator for boost::property_tree
我loading/saving一组参数from/to一个文件使用boost::property_tree
。其中许多参数是枚举(不同类型)。所以我需要一种从 boost::property_tree
获取枚举的方法(即将字符串转换为枚举),反之亦然。例如
const Enum_1 position = params.get<Enum_1>("Test.position");
我已经查看了 this answer,其中涉及为每个枚举创建一个转换器。由于我有几十个枚举,看起来有点不知所措。
当涉及许多枚举时,是否有更通用的方法来做到这一点?
PS:我将我当前的解决方案发布在一个答案中,因为我无法找到一些东西 easier/simpler。我很高兴听到更好的选择。
我当前的解决方案包括一个依赖于 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
),这些宏与名称空间或 class 中的枚举不兼容。作为解决方法,可以使用 typedef 来 重命名 枚举,或者在这些情况下不使用宏 ;).
看表头,一个好的定制点是:
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;
}
}
我们可以通过 get_string_mapping( tag_t<T>{} )
.
在任何有类型 T=bob
的地方找到这个映射
使用 can_apply
之类的东西来检测是否可以找到 get_string_mapping( tag_t<T>{} )
,使用它来使您的自定义 customize_stream
能够使用 get_string_mapping
到 load/save数据 to/from 流。
现在我们要做的就是减轻写作的痛苦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_string_mapping
并执行的 get_efficient_string_mapping
平面数据的一次性再处理。
这样做的最大好处是我们不必在全局命名空间中执行此操作;我们可以自然地将它放在枚举下或枚举的命名空间中。
我loading/saving一组参数from/to一个文件使用boost::property_tree
。其中许多参数是枚举(不同类型)。所以我需要一种从 boost::property_tree
获取枚举的方法(即将字符串转换为枚举),反之亦然。例如
const Enum_1 position = params.get<Enum_1>("Test.position");
我已经查看了 this answer,其中涉及为每个枚举创建一个转换器。由于我有几十个枚举,看起来有点不知所措。
当涉及许多枚举时,是否有更通用的方法来做到这一点?
PS:我将我当前的解决方案发布在一个答案中,因为我无法找到一些东西 easier/simpler。我很高兴听到更好的选择。
我当前的解决方案包括一个依赖于 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
),这些宏与名称空间或 class 中的枚举不兼容。作为解决方法,可以使用 typedef 来 重命名 枚举,或者在这些情况下不使用宏 ;).
看表头,一个好的定制点是:
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;
}
}
我们可以通过 get_string_mapping( tag_t<T>{} )
.
T=bob
的地方找到这个映射
使用 can_apply
之类的东西来检测是否可以找到 get_string_mapping( tag_t<T>{} )
,使用它来使您的自定义 customize_stream
能够使用 get_string_mapping
到 load/save数据 to/from 流。
现在我们要做的就是减轻写作的痛苦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_string_mapping
并执行的 get_efficient_string_mapping
平面数据的一次性再处理。
这样做的最大好处是我们不必在全局命名空间中执行此操作;我们可以自然地将它放在枚举下或枚举的命名空间中。