bloomberg/clang-p2996

Universal_formatter example

Closed this issue · 2 comments

Hi,
I figure out that universal_formatter from the proposal does not work and here is the closest I could get with the current implementation:

#include <experimental/meta>
#include <print>

template <auto... Xs, typename F> constexpr void for_values(F &&f) {
  (f.template operator()<Xs>(), ...);
}

template <auto B, auto E, typename F> constexpr void for_range(F &&f) {
  using t = std::common_type_t<decltype(B), decltype(E)>;

  [&f]<auto... Xs>(std::integer_sequence<t, Xs...>) {
    for_values<(B + Xs)...>(f);
  }(std::make_integer_sequence<t, E - B>{});
}

template <typename T> consteval auto base_info(int n) {
  return bases_of(^T)[n];
}

template <typename T> consteval auto member_info(int n) {
  return nonstatic_data_members_of(^T)[n];
}

struct universal_formatter {

  constexpr auto parse(auto &ctx) { return ctx.begin(); }

  template <typename T> auto format(T const &t, auto &ctx) const {
    std::format_to(ctx.out(), "{}{{", name_of(^T));

    auto delim = [&, first = true]() mutable {
      if (!first) {
        std::format_to(ctx.out(), ", ");
      }
      first = false;
    };

    for_range<0, bases_of(^T).size()>([&]<auto I>() {
      constexpr auto base = base_info<T>(I);
      if constexpr (is_accessible(base)) {
        delim();
        std::format_to(ctx.out(), "{}",
                       static_cast<[:type_of(base):] const &>(t));
      }
    });

    for_range<0, nonstatic_data_members_of(^T).size()>([&]<auto I>() {
      constexpr auto mem = member_info<T>(I);
      delim();
      std::format_to(ctx.out(), ".{}={}", name_of(mem), t.[:mem:]);
    });

    std::format_to(ctx.out(), "}}");
    return ctx.out();
  }
};

struct X {
  int m1 = 1;
};
struct Y {
  int m2 = 2;
};
class Z : public X, private Y {
  int m3 = 3;
  int m4 = 4;
};

template <> struct std::formatter<X> : universal_formatter {};
template <> struct std::formatter<Z> : universal_formatter {};

int main() {
  std::println("{}", X(0)); //X{.m1=0}  
  std::println("{}", Z()); // Z{X{.m1 = 1}, .m3 = 3, .m4 = 4}
  std::println("{}", []() {
    auto z = Z();
    z.m1 = -1;
    return z;
  }()); // Z{X{.m1 = -1}, .m3 = 3, .m4 = 4}
}

https://godbolt.org/z/K4cGYf8qj

One important note, is that Y is not accessible from the instance of Z so formatter can not show it.
It can be beneficial to have it as a part of tests/examples, so if you are open i can open a PR for this addition.

Hey, @Yaraslaut ! You're probably looking at P2996R2, but note that we presented a D2996R3 for the Tokyo meeting, which includes this link to a working example from Clang/P2996. We didn't implement your use of is_accessible to check for private base classes though, which is a nice trick.

Since that time, Robert Leahy brought to our attention that C-style casts allow one to circumvent access-checking on base classes, so the original Universal Formatter can in fact be saved. This example will be published in the next revision, complete with support for private base classes: https://godbolt.org/z/rbs6K78WG

That said, I did forget to add the example as a test case! If you'd like to open a PR adding the example linked above as a test case, I'd be happy to merge it.

Hey, @Yaraslaut ! You're probably looking at P2996R2, but note that we presented a D2996R3 for the Tokyo meeting, which includes this link to a working example from Clang/P2996. We didn't implement your use of is_accessible to check for private base classes though, which is a nice trick.

Since that time, Robert Leahy brought to our attention that C-style casts allow one to circumvent access-checking on base classes, so the original Universal Formatter can in fact be saved. This example will be published in the next revision, complete with support for private base classes: https://godbolt.org/z/rbs6K78WG

That said, I did forget to add the example as a test case! If you'd like to open a PR adding the example linked above as a test case, I'd be happy to merge it.

Thanks for the detailed reply, I will make a PR later.