c++ 使用boost::pfr获取字段名

krcsximq  于 2023-11-19  发布在  其他
关注(0)|答案(4)|浏览(339)

嗨,我使用boost::pfr作为基本反射,它工作正常,但问题是它只打印或处理字段值,就像boost::pfr::io一样,它打印结构体的每个成员,但我如何将其打印为名称值对,for_each_field也有同样的问题,函子只接受值,但不接受名称。我如何获得字段名称?

  1. struct S {
  2. int n;
  3. std::string name;
  4. };
  5. S o{1, "foo"};
  6. std::cout << boost::pfr::io(o);
  7. // Outputs: {1, "foo"}, how can I get n = 1, name = "foo"?

字符串

7cwmlq89

7cwmlq891#

如果你认为修改一个结构体不会造成太大的干扰(它不会改变你现有的定义,你甚至不需要把它放在公共头中):

  1. BOOST_FUSION_ADAPT_STRUCT(S, n, name)

字符串
然后你可以为序列构造一个通用的operator<<

  1. namespace BF = boost::fusion;
  2. template <typename T,
  3. typename Enable = std::enable_if_t<
  4. // BF::traits::is_sequence<T>::type::value>
  5. std::is_same_v<BF::struct_tag, typename BF::traits::tag_of<T>::type>>>
  6. std::ostream& operator<<(std::ostream& os, T const& v)
  7. {
  8. bool first = true;
  9. auto visitor = [&]<size_t I>() {
  10. os << (std::exchange(first, false) ? "" : ", ")
  11. << BF::extension::struct_member_name<T, I>::call()
  12. << " = " << BF::at_c<I>(v);
  13. };
  14. // visit members
  15. [&]<size_t... II>(std::index_sequence<II...>)
  16. {
  17. return ((visitor.template operator()<II>(), ...);
  18. }
  19. (std::make_index_sequence<BF::result_of::size<T>::type::value>{});
  20. return os;
  21. }

  • (在c++20之前,这需要一些显式的模板类型,而不是paddas,也许会使它更具可读性。我想我是懒惰的......)*

这里有一个现场演示:Live On Compiler Explorer

  1. n = 1, name = foo

额外好处:正确引用类字符串类型

Live On Compiler Explorer

  1. #include <boost/fusion/include/adapt_struct.hpp>
  2. #include <boost/fusion/include/for_each.hpp>
  3. #include <boost/fusion/include/at_c.hpp>
  4. #include <iostream>
  5. #include <iomanip>
  6. namespace MyLib {
  7. struct S {
  8. int n;
  9. std::string name;
  10. };
  11. namespace BF = boost::fusion;
  12. static auto inline pretty(std::string_view sv) { return std::quoted(sv); }
  13. template <typename T,
  14. typename Enable = std::enable_if_t<
  15. not std::is_constructible_v<std::string_view, T const&>>>
  16. static inline T const& pretty(T const& v)
  17. {
  18. return v;
  19. }
  20. template <typename T,
  21. typename Enable = std::enable_if_t<
  22. // BF::traits::is_sequence<T>::type::value>
  23. std::is_same_v<BF::struct_tag, typename BF::traits::tag_of<T>::type>>>
  24. std::ostream& operator<<(std::ostream& os, T const& v)
  25. {
  26. bool first = true;
  27. auto visitor = [&]<size_t I>() {
  28. os << (std::exchange(first, false) ? "" : ", ")
  29. << BF::extension::struct_member_name<T, I>::call()
  30. << " = " << pretty(BF::at_c<I>(v));
  31. };
  32. // visit members
  33. [&]<size_t... II>(std::index_sequence<II...>)
  34. {
  35. return (visitor.template operator()<II>(), ...);
  36. }
  37. (std::make_index_sequence<BF::result_of::size<T>::type::value>{});
  38. return os;
  39. }
  40. } // namespace MyLib
  41. BOOST_FUSION_ADAPT_STRUCT(MyLib::S, n, name)
  42. int main()
  43. {
  44. MyLib::S o{1, "foo"};
  45. std::cout << o << "\n";
  46. }


产出:

  1. n = 1, name = "foo"

展开查看全部
dy1byipe

dy1byipe2#

库不能提供任何这样的功能,因为目前不可能获得类成员的名称作为对象的值。
如果要输出字段名,则需要声明与成员Map的字符串对象,并实现手动使用这些字符串的operator<<
要做到这一点,一个更复杂的反射库可能会提供宏来定义成员。宏可以使用提供的名称作为标识符将其参数扩展到声明中,同时也可以使用名称作为字符串字面量(通过#宏替换操作符)生成代码。

6qfn3psc

6qfn3psc3#

这是愚蠢的,但嘿,与字符串化宏每个字段可能是足够的你。

  • C++14,没有额外的库 *
  1. #include <boost/pfr.hpp>
  2. struct S
  3. {
  4. int n;
  5. std::string name;
  6. static char const* const s_memNames[2];
  7. };
  8. char const* const S::s_memNames[2] = {"n", "name"};
  9. // utility
  10. template< size_t I, typename TR >
  11. char const* MemberName()
  12. {
  13. using T = std::remove_reference_t<TR>;
  14. if (I < std::size(T::s_memNames))
  15. return T::s_memNames[I];
  16. return nullptr;
  17. }
  18. // test:
  19. #include <iostream>
  20. using std::cout;
  21. template< size_t I, typename T >
  22. void StreamAt(T&& inst)
  23. {
  24. char const* n = MemberName<I,T>();
  25. auto& v = boost::pfr::get<I>(inst);
  26. cout << "(" << n << " = " << v << ")";
  27. }
  28. int main()
  29. {
  30. S s{2, "boo"};
  31. boost::pfr::for_each_field(s, [&](const auto&, auto I)
  32. {
  33. StreamAt<decltype(I)::value>(s);
  34. cout << "\n";
  35. });
  36. }

字符串
产出:
(n = 2)
(name(嘘)

  • (以前版本的建议,这一个有更多的绒毛,所以不太有趣)*
  1. #include <boost/pfr.hpp>
  2. // library additions:
  3. static char const* g_names[100];
  4. template< size_t V >
  5. struct Id : std::integral_constant<size_t, V > {};
  6. template< size_t I, typename T >
  7. using TypeAt = boost::pfr::tuple_element_t<I, T>;
  8. template<std::size_t Pos, class Struct>
  9. constexpr int Ni() // name index
  10. {
  11. return std::tuple_element_t<Pos, typename std::remove_reference_t<Struct>::NamesAt >::value;
  12. }
  13. struct StaticCaller
  14. {
  15. template< typename Functor >
  16. StaticCaller(Functor f) { f();}
  17. };
  18. ///
  19. /// YOUR CODE HERE
  20. struct S
  21. {
  22. using NamesAt = std::tuple<Id<__COUNTER__>, Id<__COUNTER__>>; // add this
  23. int n;
  24. std::string name;
  25. static void Init() // add this
  26. {
  27. g_names[Ni<0,S>()] = "n";
  28. g_names[Ni<1,S>()] = "name";
  29. }
  30. };
  31. StaticCaller g_sc__LINE__(S::Init); // add this
  32. // utilities
  33. template< size_t I, typename T >
  34. auto GetValueName(T&& inst)
  35. {
  36. return std::make_pair(boost::pfr::get<I>(inst), g_names[Ni<I,T>()]);
  37. }
  38. // test:
  39. #include <iostream>
  40. using std::cout;
  41. template< size_t I, typename T >
  42. void StreamAt(T&& inst)
  43. {
  44. auto const& [v,n] = GetValueName<I>(inst);
  45. cout << "(" << v << ", " << n << ")";
  46. }
  47. int main()
  48. {
  49. S s{2, "boo"};
  50. boost::pfr::for_each_field(s, [&](const auto&, auto I)
  51. {
  52. StreamAt<decltype(I)::value>(s);
  53. cout << "\n";
  54. });
  55. }


输出
(2,n)
(boo,姓名)

展开查看全部
jobtbby3

jobtbby34#

这在boost 1.84中是可能的,在最新版本的pfr git repo中也是可能的。你可以使用像boost::pfr::names_as_array<Type>()boost::pfr::get_name<N, Type>这样的函数(我在使用今天的预发布代码构建后者时遇到了一些问题,但是names_as_array在所有三大编译器上都解决了)。请参阅文档。
(for有关其工作原理的说明,请参阅this answer by Artyer

相关问题