Boost Fusion:将适配的结构类型转换为文本

4
给定这样的结构体:
struct Foo
{
    int x;
    int y;
    double z;
};

BOOST_FUSION_ADAPT_STRUCT(Foo, x, y, z);       

我想生成这样的字符串:
"{ int x; int y; double z; }"

我看到了如何打印Fusion适配结构体的,但是这里我只需要打印类型和名称。
我应该如何最简单地做到这一点?如果有更好的方法,我并不固执于使用Boost.Fusion。
2个回答

5
我认为你可以通过对这个答案中的代码进行一些微小的修改来获得类似于你想要的东西。你可以很容易地使用boost::fusion::extension::struct_member_name来获取成员名称,但据我所知,你无法直接获取成员类型名称。你可以使用boost::fusion::result_of::value_at(以及其他选项)来获取成员类型,并且我选择使用Boost.TypeIndex来获取其名称(在编译器和相关类型的情况下,它的美观程度不同)。所有这些都是基于你实际需要Fusion适配的假设,如果你不需要,那么你可能会得到一个更简单的方法来完成你所需的操作。
完整代码: 在WandBox上运行(gcc) 在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);
}

这个很好用,谢谢。你说的“如果你不需要Fusion适配,可能可以采用更简单的方法”,是指我可以自己定义宏而不使用Fusion吗? - John Zwinck
从你的问题中我不知道你的实际用例,我可以想象一种情况,你已经在使用适应的结构并希望另外打印它的表示形式,在这种情况下这是一个很好的解决方案;但我也可以看到一种情况,你只是使用Fusion因为它具有“反射”/“内省”功能而没有使用其他任何东西,在这种情况下,适应性所做的工作比你实际需要的要多得多。 - llonesmiz
明白了。谢谢。 - John Zwinck

0
您可以使用以下解决方案,它依赖于编译器(已在clang / gcc / MSVC上测试),并且仅适用于具有c++14的情况(在稍作修改后应该适用于c++11)。它可以实现您想要的功能,但可能存在更简单的解决方案...
第一部分是一些编译器相关代码,用于解码由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

然后,“通用”部分非常简短:

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

这个想法是创建一个继承自指定类(例如Foo)的类L,并使用__VA_ARGS__宏将属性名称扩展为std::make_tuple以获取它们的类型。

to_stringtuple中检索每个元素的std::type_info::name,并将其与属性名称(例如"x, y, z")组合起来。

CREATE宏返回一个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>

注意:使用“复杂”,以支持MSVC:最初我在CREATE内部使用了一个lambda,这样您就可以执行CREATE(Foo, x, y, z)()而不必费心创建变量(我不知道如何生成正确的名称-请参见此答案的初始版本),但是MSVC不喜欢lambda内部的decltype(std::make_tuple(x, y, z))...(可能是一个错误)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接