3

Hi I'm using boost::pfr for basic reflection, it works fine, but the problem is it is only print or deal with the field values, like with boost::pfr::io it prints each member of the struct, but how can I print it as name value pairs, same issue with for_each_field, the functor only accepts values, but not names. How can I get the field names?

struct S {
    int n;
    std::string name;
};
S o{1, "foo"};
std::cout << boost::pfr::io(o);
// Outputs: {1, "foo"}, how can I get n = 1, name = "foo"?
fluter
  • 13,238
  • 8
  • 62
  • 100
  • 1
    You can not do so with boost::pfr. You need some kind of macro registering for the class, as in the example using boost::fusion provided by Sehe. Anyway, in case you want something more modern I would look to boost::hana, where you can find an example doing that precisely: https://stackoverflow.com/questions/37912964/printing-the-values-of-an-instantiated-structure-without-using-the-names-of-the – Pablo Jan 24 '22 at 18:08

3 Answers3

5

If you think adapting a struct is not too intrusive (it doesn't change your existing definitions, and you don't even need to have it in a public header):

BOOST_FUSION_ADAPT_STRUCT(S, n, name)

Then you can concoct a general operator<< for sequences:

namespace BF = boost::fusion;

template <typename T,
          typename Enable = std::enable_if_t<
              // BF::traits::is_sequence<T>::type::value>
              std::is_same_v<BF::struct_tag, typename BF::traits::tag_of<T>::type>>>
std::ostream& operator<<(std::ostream& os, T const& v)
{
    bool first = true;
    auto visitor = [&]<size_t I>() {
        os << (std::exchange(first, false) ? "" : ", ")
           << BF::extension::struct_member_name<T, I>::call()
           << " = " << BF::at_c<I>(v);
    };

    // visit members
    [&]<size_t... II>(std::index_sequence<II...>)
    {
        return ((visitor.template operator()<II>(), ...);
    }
    (std::make_index_sequence<BF::result_of::size<T>::type::value>{});
    return os;
}

(Prior to c++20 this would require some explicit template types instead of the lambdas, perhaps making it more readable. I guess I'm lazy...)

Here's a live demo: Live On Compiler Explorer

n = 1, name = foo

Bonus: Correctly quoting string-like types

Live On Compiler Explorer

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <boost/fusion/include/at_c.hpp>
#include <iostream>
#include <iomanip>

namespace MyLib {
    struct S {
        int         n;
        std::string name;
    };

    namespace BF = boost::fusion;

    static auto inline pretty(std::string_view sv) { return std::quoted(sv); }

    template <typename T,
            typename Enable = std::enable_if_t<
                not std::is_constructible_v<std::string_view, T const&>>>
    static inline T const& pretty(T const& v)
    {
        return v;
    }

    template <typename T,
            typename Enable = std::enable_if_t<
                // BF::traits::is_sequence<T>::type::value>
                std::is_same_v<BF::struct_tag, typename BF::traits::tag_of<T>::type>>>
    std::ostream& operator<<(std::ostream& os, T const& v)
    {
        bool first = true;
        auto visitor = [&]<size_t I>() {
            os << (std::exchange(first, false) ? "" : ", ")
            << BF::extension::struct_member_name<T, I>::call()
            << " = " << pretty(BF::at_c<I>(v));
        };

        // visit members
        [&]<size_t... II>(std::index_sequence<II...>)
        {
            return (visitor.template operator()<II>(), ...);
        }
        (std::make_index_sequence<BF::result_of::size<T>::type::value>{});
        return os;
    }
} // namespace MyLib

BOOST_FUSION_ADAPT_STRUCT(MyLib::S, n, name)

int main()
{
    MyLib::S o{1, "foo"};
    std::cout << o << "\n";
}

Outputs:

n = 1, name = "foo"
Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
1

The library cannot offer any such functionality because it is currently impossible to obtain the name of a member of a class as value of an object.

If you want to output field names, you need to declare string objects mapped with the members and implement a operator<< which uses these strings manually.

To do this a more sophisticated reflection library would probably offer macros to use in the definition of the members. Macros can expand their argument(s) into a declaration using the provided name as identifier while also producing code using the name as string literal (via the # macro replacement operator).

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • i'd like to do it non-intrusive way, using macros end up modify the struct definitions. if i do that there is no point to use pfr at all. – fluter Jan 23 '22 at 12:37
  • @fluter Then I think the only possibility is to define an `operator<<` overload or similar manually. You can put that in a macro as well and put it outside the struct definition. This kind of reflection accessing identifiers isn't really possible in C++. (With some exceptions like type names in `typeid`.) – user17732522 Jan 23 '22 at 12:43
0

It's stupid but hey, with a stringifying macro per field it could be enough for you.

C++14, no additional library

#include <boost/pfr.hpp>

struct S
{
    int n;
    std::string name;

    static char const* const s_memNames[2];
};
char const* const S::s_memNames[2] = {"n", "name"};

// utility
template< size_t I, typename TR >
char const* MemberName()
{
    using T = std::remove_reference_t<TR>;
    if (I < std::size(T::s_memNames))
        return T::s_memNames[I];
    return nullptr;
}


// test:

#include <iostream>

using std::cout;

template< size_t I, typename T >
void StreamAt(T&& inst)
{
    char const* n = MemberName<I,T>();
    auto& v = boost::pfr::get<I>(inst);
    cout << "(" << n << " = " << v << ")";
}

int main()
{
    S s{2, "boo"};

    boost::pfr::for_each_field(s, [&](const auto&, auto I)
                               {
                                   StreamAt<decltype(I)::value>(s);
                                   cout << "\n";
                               });
}

output:

(n = 2)
(name = boo)


(previous version of the suggestion, this one has more fluff so less interesting)

#include <boost/pfr.hpp>


// library additions:
static char const* g_names[100];

template< size_t V >
struct Id : std::integral_constant<size_t, V > {};

template< size_t I, typename T >
using TypeAt = boost::pfr::tuple_element_t<I, T>;

template<std::size_t Pos, class Struct>
constexpr int Ni()  // name index
{
    return std::tuple_element_t<Pos, typename std::remove_reference_t<Struct>::NamesAt >::value;
}

struct StaticCaller
{
    template< typename Functor >
    StaticCaller(Functor f) { f();}
};

///
/// YOUR CODE HERE
struct S
{
    using NamesAt = std::tuple<Id<__COUNTER__>, Id<__COUNTER__>>;  // add this

    int n;
    std::string name;

    static void Init()  // add this
    {
        g_names[Ni<0,S>()] = "n";
        g_names[Ni<1,S>()] = "name";
    }
};
StaticCaller g_sc__LINE__(S::Init);  // add this


// utilities
template< size_t I, typename T >
auto GetValueName(T&& inst)
{
    return std::make_pair(boost::pfr::get<I>(inst), g_names[Ni<I,T>()]);
}


// test:

#include <iostream>

using std::cout;

template< size_t I, typename T >
void StreamAt(T&& inst)
{
    auto const& [v,n] = GetValueName<I>(inst);
    cout << "(" << v << ", " << n << ")";
}

int main()
{
    S s{2, "boo"};

    boost::pfr::for_each_field(s, [&](const auto&, auto I)
                               {
                                   StreamAt<decltype(I)::value>(s);
                                   cout << "\n";
                               });
}

output

(2, n)
(boo, name)

v.oddou
  • 6,476
  • 3
  • 32
  • 63