Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A tiny bit closer to the STL? #140

Closed
polyvertex opened this issue Mar 19, 2015 · 3 comments
Closed

A tiny bit closer to the STL? #140

polyvertex opened this issue Mar 19, 2015 · 3 comments

Comments

@polyvertex
Copy link
Contributor

I defined a custom STL container derived from std::basic_string<CharT>. Some quick digging into cppformat's source showed that I cannot ask cppformat to write directly into my container, requiring me to use a BasicWriter derived instance (i.e.: BasicMemoryWriter).

Every options I found or could imagine requires the use of a temporary buffer that will finally be copied into my container. I also investigated the "derivate BasicWriter" option in order to achieve that but it isn't clear to me what method(s) I would have to override. Several it appears.

I absolutely have no clue of the feasibility/complexity of this but since it seems cppformat just write/append data to the destination container, so an additional formatting function that accepts a std::back_inserter iterator would definitely be nice. Something like:

(void)fmt::print(std::back_inserter(my_dest_container), "Elapsed time: {0:.2f} seconds", 1.23);

As an example, the utf8-cpp library uses this technique and it is quite clean and straightforward.

Or maybe is there a clean way to derivate BasicWriter or other? I would be happy to submit a patch.

Thanks for your utterly useful lib anyway :)

@vitaut
Copy link
Contributor

vitaut commented Mar 19, 2015

You can provide a buffer for writing directly into your custom container. Here's an example of a buffer for writing into std::string, but it should be similar for other containers:

#include "format.h"

class StringBuffer : public fmt::internal::Buffer<char> {
 private:
  std::string buffer_;

 protected:
  void grow(std::size_t size) {
    buffer_.resize(size);
    ptr_ = &buffer_[0];
    capacity_ = size;
  }

 public:
  std::string &str() { return buffer_; }
};

class StringWriter : public fmt::Writer {
 private:
  StringBuffer buffer_;

 public:
  StringWriter() : fmt::Writer(buffer_) {}

  std::string &str() { return buffer_.str(); }
};

int main() {
  StringWriter w;
  w.write("Hello, {}!", "world");
  w.str(); // get the output string
}

Currently this requires deriving from an internal class (fmt::internal::Buffer), but if you are interested, I can move it into the public API.

Hope this works for you.

As for supporting general iterators, it will require some modification to the library and I'm not sure that it's worth the trouble as buffers are usually represented by contiguous regions of memory.

@polyvertex
Copy link
Contributor Author

I followed your example and it worked like a charm. I also implemented a format() method into my container using the FMT_VARIADIC macro.

I can't tell if you should move fmt::internal::Buffer into the public API or not. Or even maybe think about a different way of offering such a feature. But it was definitely the feature I was looking for. So maybe adding a bottom section to the documentation would just do it? OTOH, writing fmt::internal::... in the documentation would feel a bit dirty though... :)

Kudos for writing C++ Format the way you wrote it. It offers the features I was missing with other libraries, it's easy to integrate and a breeze to use.

Here are the final helper classes in case someone stumble upon the same requirements:

EDIT: this is an even more generic way so one can optionally append data to the container instead of discarding existing data.

template <class Container>
class FmtBuffer : public ::fmt::internal::Buffer<typename Container::value_type>
{
public:
    FmtBuffer(Container& dest, typename Container::size_type initial_size=0)
        : buffer_(dest)
        , initial_size_(initial_size)
        , ::fmt::internal::Buffer<typename Container::value_type>(
            &dest[initial_size])
        { }

protected:
    void grow(std::size_t size)
    {
        buffer_.resize(initial_size_ + size);
        ptr_ = &buffer_[initial_size_];
        capacity_ = size;
    }

private:
    Container& buffer_;
    typename Container::size_type initial_size_;
};

template <class Container>
class FmtAppender : public ::fmt::BasicWriter<typename Container::value_type>
{
public:
    FmtAppender(Container& dest)
        : buffer_(dest, dest.size())
        , ::fmt::BasicWriter<typename Container::value_type>(buffer_)
        { }

private:
    FmtBuffer<Container> buffer_;
};

MyContainer& MyContainer::fmt(fmt::BasicStringRef<MyContainer::value_type> format, fmt::ArgList args)
{
    this->clear();
    this->fmt_append(format, args);
    return *this;
}

MyContainer& MyContainer::fmt_append(fmt::BasicStringRef<MyContainer::value_type> format, fmt::ArgList args)
{
    detail::FmtAppender<MyContainer> appender(*this);
    appender.write(format, args);
    return *this;
}

@vitaut
Copy link
Contributor

vitaut commented Mar 20, 2015

Thanks for the nice feedback. I made Buffer part of the public API so it is now in fmt instead of the fmt::internal namespace.

Preliminary documentation is available here: http://cppformat.github.io/dev/api.html#_CPPv2N3fmt6BufferE.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants