fmtlib / fmt

A modern formatting library
https://fmt.dev
Other
19.9k stars 2.43k forks source link

formatting of variant types #3934

Closed cruisercoder closed 2 months ago

cruisercoder commented 3 months ago

For garden variety user variant types that hold other types (with private inheritance, for example), what is the suggested way to implement calls for formatting of the underlying types? Can I do better than calling a function that returns the string value of format() of the underlying typed value? Is there a better runtime mechanism that uses the format_context in some way?

class Variant {
public:
  template <class X> Variant(const X &x) : ptr_(std::make_shared<model<X>>(x)) {}
  std::string format() const { return ptr_->format(); }

private:
  struct base {
    virtual ~base() {}
    virtual std::string format() const = 0;
  };

  template <class X> struct model : public base {
    model(const X &x) : x_(x) {}
    std::string format() const override { return fmt::format("{}", x_); }
    X x_;
  };

  using ptr_t = std::shared_ptr<base>;
  ptr_t ptr_;
};

template <> struct fmt::formatter<Variant> {
  constexpr auto parse(fmt::format_parse_context &context) {
    return context.begin();
  }
  auto format(const Variant &v, fmt::format_context &ctx) const {
    return format_to(ctx.out(), "{}", v.format()); // something better?
  }
};
vitaut commented 2 months ago

You could pass format_context or format_context::iterator to the virtual format functions and format directly into the underlying buffer via format_to instead of a temporary string.

cruisercoder commented 2 months ago

That makes sense. The part that was unclear is the construction of the OutputIt return type of the formatter format function (and whether it needed to be returned from the inner format). It looks the return value is for convenience and it's happy to construct from ctx.out(). Updated code below.

class Variant {
public:
  template <class X> Variant(const X &x) : ptr_(std::make_shared<model<X>>(x)) {}
  void format(fmt::format_context &ctx) const { return ptr_->format(ctx); }

private:
  struct base {
    virtual ~base() {}
    virtual void format(fmt::format_context &ctx) const = 0;
  };

  template <class X> struct model : public base {
    model(const X &x) : x_(x) {}
    void format(fmt::format_context &ctx) const override {fmt::format_to(ctx.out(), "{}", x_); }
    X x_;
  };

  using ptr_t = std::shared_ptr<base>;
  ptr_t ptr_;
};

template <> struct fmt::formatter<Variant> {
  constexpr auto parse(fmt::format_parse_context &context) { return context.begin(); }
  auto format(const Variant &v, fmt::format_context &ctx) const {
    v.format(ctx);
    return ctx.out();
  }
};
vitaut commented 2 months ago

You don't have to return the iterator in this case because format_context::iterator is like a back_insert_iterator into an output buffer.