【问题标题】:Boost Fusion: convert adapted struct type to textBoost Fusion:将适应的结构类型转换为文本
【发布时间】:2016-12-26 06:20:03
【问题描述】:

给定这样的结构:

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 融合适应结构,但在这里我只需要打印类型和名称。

我怎样才能最简单地做到这一点?如果有更好的方法,我不会嫁给 Boost.Fusion。

【问题讨论】:

    标签: c++ introspection boost-fusion


    【解决方案1】:

    您可以使用以下依赖于编译器的解决方案(在 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>{}); \
        }; \
    } 
    

    想法是创建一个类L,它继承自指定的类(例如Foo),并使用__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&lt;int, 10&gt;

    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)) ...(可能是一个错误)。

    【讨论】:

      【解决方案2】:

      我认为您可以通过对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);
      }
      

      【讨论】:

      • 这很好用,谢谢。您所说的“如果您不需要 Fusion 适配,您可能会得到更简单的方法”是什么意思?你的意思是定义我自己的宏而不使用 Fusion?
      • 从你的问题我不知道你的实际用例,我可以想象一个你已经在使用调整后的结构并想另外打印它的表示的情况,在这种情况下这是一个很好的解决方案;但我也可以看到你只使用 Fusion 的情况,因为它的“反射”/“内省”功能没有使用其他任何东西,在这种情况下,适应所做的工作比你实际需要的要多得多。
      • 明白。谢谢。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多