Boost Fusion:将适应的结构类型转换为文本
Boost Fusion: convert adapted struct type to text
给出这样的结构:
struct Foo
{
int x;
int y;
double z;
};
BOOST_FUSION_ADAPT_STRUCT(Foo, x, y, z);
我想生成这样的字符串:
"{ int x; int y; double z; }"
我已经了解了如何 print the values Fusion 改编的结构,但在这里我只需要打印类型和名称。
我怎样才能简单地做到这一点?如果有更好的方法,我不嫁给Boost.Fusion
您可以使用以下依赖于编译器的解决方案(在 clang / gcc / MSVC 上测试)并且仅在您拥有 c++14 时才有效(在稍作修改后应与 c++11 一起使用)。它可以满足您的需求,但可能还有更简单的解决方案...
第一部分是一些依赖于编译器的代码,用于 demangle names return by std::type_info::name
:
#include <string>
#if defined __GNUC__
#include <cxxabi.h>
std::string demangle (const char *name) {
int status = 0;
return abi::__cxa_demangle(name, 0, 0, &status);
}
#elif defined _WIN32
#include <Windows.h>
#include <DbgHelp.h>
std::string demangle (const char *name) {
char buffer[1024];
UnDecorateSymbolName(name, buffer, sizeof(buffer)/sizeof(*buffer), 0);
return buffer;
}
#endif
然后 "generic" 部分很短:
#include <array>
#include <tuple>
template <typename Tuple, size_t ...Idx>
std::string to_string (std::string vars, std::index_sequence<Idx...>) {
std::array<const char *, std::tuple_size<Tuple>::value> tnames{
typeid(typename std::tuple_element<Idx, Tuple>::type).name()...};
std::stringstream res;
res << "{ ";
for (auto s: tnames) {
size_t end = vars.find(',');
res << demangle(s) << ' ' << vars.substr(0, end) << "; ";
vars = vars.substr(end + 2);
}
res << '}';
return res.str();
}
#define CREATE(S, ...) struct: S { \
using Tuple = decltype(std::make_tuple(__VA_ARGS__)); \
std::string operator()() { \
return to_string<Tuple>(#__VA_ARGS__, \
std::make_index_sequence<std::tuple_size<Tuple>::value>{}); \
}; \
}
想法是创建一个class L
继承自指定的class(例如Foo
)并使用__VA_ARGS__
宏来扩展将属性名称放入 std::make_tuple
以获得它们的类型。
to_string
从 tuple
中检索每个元素的 std::type_info::name
并将其与属性名称组合(例如 "x, y, z"
)。
CREATE
宏 return 是一个 lambda,您可以按如下方式使用它:
struct Foo {
int x;
int y;
double z;
};
CREATE(Foo, x, y, z) foo_xyz;
#include <iostream>
int main () {
std::cout << foo_xyz() << std::endl;
}
输出:
{ int x; int y; double z; }
注意:由于分解依赖于编译器,您可能不会在所有编译器上获得完全相同的输出...例如,如果您有一个 std::array<int, 10>
:
gcc: std::array<int, 10ul>
clang: std::__1::array<int, 10ul>
msvc: class std::array<int,10>
注意: 用法是 "complicated" 以支持 MSVC:最初我在 CREATE
中使用了一个 lambda,这样你就可以 CREATE(Foo, x, y, z)()
不必费心创建变量(我不知道如何生成正确的名称 - 请参阅此答案的初始版本),但 MSVC 不喜欢 lambda 中的 decltype(std::make_tuple(x, y, z))
...(可能是一个错误)。
我认为您可以通过对 this answer 中的代码进行一些细微的修改来获得与您想要的类似的东西。您可以使用 boost::fusion::extension::struct_member_name
轻松获取成员名称,但据我所知,您无法直接获取成员类型名称。您可以使用 boost::fusion::result_of::value_at
(在其他选项中)获取成员类型,我选择使用 Boost.TypeIndex 获取其名称(不同程度的漂亮,取决于编译器和相关类型) .所有这一切都假设您确实需要 Fusion 改编,如果您不需要,您可能会得到一个更简单的方法,只做您需要的事情。
完整代码
Running on WandBox (gcc)
Running on rextester (vc)
#include <iostream>
#include <string>
#include <boost/mpl/range_c.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <boost/fusion/include/zip.hpp>
#include <boost/fusion/include/at_c.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/mpl.hpp>
#include <boost/type_index.hpp>
namespace fusion=boost::fusion;
namespace mpl=boost::mpl;
struct Foo
{
int x;
int y;
double z;
};
BOOST_FUSION_ADAPT_STRUCT(Foo, x, y, z);
struct Bar
{
std::pair<int,int> p;
std::string s;
};
BOOST_FUSION_ADAPT_STRUCT(Bar, p, s);
template <typename Sequence>
struct Struct_member_printer
{
Struct_member_printer(const Sequence& seq):seq_(seq){}
const Sequence& seq_;
template <typename Index>
void operator() (Index) const
{
std::string member_type = boost::typeindex::type_id<typename fusion::result_of::value_at<Sequence,Index>::type >().pretty_name() ;
std::string member_name = fusion::extension::struct_member_name<Sequence,Index::value>::call();
std::cout << member_type << " " << member_name << "; ";
}
};
template<typename Sequence>
void print_struct(Sequence const& v)
{
typedef mpl::range_c<unsigned, 0, fusion::result_of::size<Sequence>::value > Indices;
std::cout << "{ ";
fusion::for_each(Indices(), Struct_member_printer<Sequence>(v));
std::cout << "}\n";
}
int main()
{
Foo foo;
print_struct(foo);
Bar bar;
print_struct(bar);
}
给出这样的结构:
struct Foo
{
int x;
int y;
double z;
};
BOOST_FUSION_ADAPT_STRUCT(Foo, x, y, z);
我想生成这样的字符串:
"{ int x; int y; double z; }"
我已经了解了如何 print the values Fusion 改编的结构,但在这里我只需要打印类型和名称。
我怎样才能简单地做到这一点?如果有更好的方法,我不嫁给Boost.Fusion
您可以使用以下依赖于编译器的解决方案(在 clang / gcc / MSVC 上测试)并且仅在您拥有 c++14 时才有效(在稍作修改后应与 c++11 一起使用)。它可以满足您的需求,但可能还有更简单的解决方案...
第一部分是一些依赖于编译器的代码,用于 demangle names return by std::type_info::name
:
#include <string>
#if defined __GNUC__
#include <cxxabi.h>
std::string demangle (const char *name) {
int status = 0;
return abi::__cxa_demangle(name, 0, 0, &status);
}
#elif defined _WIN32
#include <Windows.h>
#include <DbgHelp.h>
std::string demangle (const char *name) {
char buffer[1024];
UnDecorateSymbolName(name, buffer, sizeof(buffer)/sizeof(*buffer), 0);
return buffer;
}
#endif
然后 "generic" 部分很短:
#include <array>
#include <tuple>
template <typename Tuple, size_t ...Idx>
std::string to_string (std::string vars, std::index_sequence<Idx...>) {
std::array<const char *, std::tuple_size<Tuple>::value> tnames{
typeid(typename std::tuple_element<Idx, Tuple>::type).name()...};
std::stringstream res;
res << "{ ";
for (auto s: tnames) {
size_t end = vars.find(',');
res << demangle(s) << ' ' << vars.substr(0, end) << "; ";
vars = vars.substr(end + 2);
}
res << '}';
return res.str();
}
#define CREATE(S, ...) struct: S { \
using Tuple = decltype(std::make_tuple(__VA_ARGS__)); \
std::string operator()() { \
return to_string<Tuple>(#__VA_ARGS__, \
std::make_index_sequence<std::tuple_size<Tuple>::value>{}); \
}; \
}
想法是创建一个class L
继承自指定的class(例如Foo
)并使用__VA_ARGS__
宏来扩展将属性名称放入 std::make_tuple
以获得它们的类型。
to_string
从 tuple
中检索每个元素的 std::type_info::name
并将其与属性名称组合(例如 "x, y, z"
)。
CREATE
宏 return 是一个 lambda,您可以按如下方式使用它:
struct Foo {
int x;
int y;
double z;
};
CREATE(Foo, x, y, z) foo_xyz;
#include <iostream>
int main () {
std::cout << foo_xyz() << std::endl;
}
输出:
{ int x; int y; double z; }
注意:由于分解依赖于编译器,您可能不会在所有编译器上获得完全相同的输出...例如,如果您有一个 std::array<int, 10>
:
gcc: std::array<int, 10ul>
clang: std::__1::array<int, 10ul>
msvc: class std::array<int,10>
注意: 用法是 "complicated" 以支持 MSVC:最初我在 CREATE
中使用了一个 lambda,这样你就可以 CREATE(Foo, x, y, z)()
不必费心创建变量(我不知道如何生成正确的名称 - 请参阅此答案的初始版本),但 MSVC 不喜欢 lambda 中的 decltype(std::make_tuple(x, y, z))
...(可能是一个错误)。
我认为您可以通过对 this answer 中的代码进行一些细微的修改来获得与您想要的类似的东西。您可以使用 boost::fusion::extension::struct_member_name
轻松获取成员名称,但据我所知,您无法直接获取成员类型名称。您可以使用 boost::fusion::result_of::value_at
(在其他选项中)获取成员类型,我选择使用 Boost.TypeIndex 获取其名称(不同程度的漂亮,取决于编译器和相关类型) .所有这一切都假设您确实需要 Fusion 改编,如果您不需要,您可能会得到一个更简单的方法,只做您需要的事情。
完整代码
Running on WandBox (gcc)
Running on rextester (vc)
#include <iostream>
#include <string>
#include <boost/mpl/range_c.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <boost/fusion/include/zip.hpp>
#include <boost/fusion/include/at_c.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/mpl.hpp>
#include <boost/type_index.hpp>
namespace fusion=boost::fusion;
namespace mpl=boost::mpl;
struct Foo
{
int x;
int y;
double z;
};
BOOST_FUSION_ADAPT_STRUCT(Foo, x, y, z);
struct Bar
{
std::pair<int,int> p;
std::string s;
};
BOOST_FUSION_ADAPT_STRUCT(Bar, p, s);
template <typename Sequence>
struct Struct_member_printer
{
Struct_member_printer(const Sequence& seq):seq_(seq){}
const Sequence& seq_;
template <typename Index>
void operator() (Index) const
{
std::string member_type = boost::typeindex::type_id<typename fusion::result_of::value_at<Sequence,Index>::type >().pretty_name() ;
std::string member_name = fusion::extension::struct_member_name<Sequence,Index::value>::call();
std::cout << member_type << " " << member_name << "; ";
}
};
template<typename Sequence>
void print_struct(Sequence const& v)
{
typedef mpl::range_c<unsigned, 0, fusion::result_of::size<Sequence>::value > Indices;
std::cout << "{ ";
fusion::for_each(Indices(), Struct_member_printer<Sequence>(v));
std::cout << "}\n";
}
int main()
{
Foo foo;
print_struct(foo);
Bar bar;
print_struct(bar);
}