Boost logo

Boost :

From: Roberto Hinz (robhz786_at_[hidden])
Date: 2019-08-29 13:24:21

Hi all,

I haven't been succesful at attracting interest in a formatting
library [1] I've been working lately. But recently I realized that
part of it could be isolated as a small standalone library that
could solve an old common troublesome situation in C++:

Suppose you need to create a function that returns/provides a string
whose content and size is unknown at compilation time. The first
approach is to make it return a `std::string`. But if it need to be
usable in environments like bare-metal real-time system,
then one usually makes it take a raw string as an output argument,
more or less like this:

  struct result{ char* it; bool truncated; };
  result get_message(char* dest, std::size_t dest_len);

But this is clearly not a perfect solution since there's nothing
really effective the caller can do when get_mesage fails because
of the destination string being is too small.

So I present the `outbut` abstract class. It somehow resembles
`std::streambuf`, but with a simpler and lower level design,
which is the result of many attempts looking for the best
performance [2] and usability in my formatting library.
Afaics, it does not require a hosted C++ implementation,
though I would like someone else to confirm that.

Now the caller of `get_message` has to choose or create a
suitable class type deriving from outbuf, that dictates where
the message is written to. For example, if the user wants to
get a `std::string`, then `string_maker` will do the job:

  #include <boost/outbuf/string.hpp>

  // ...
      boost::outbuf::string_maker<false> msg;
      std::string str = msg.finish();

Or, if one wants it to write into a raw string,
then use `cstr_writer`

    char buff[buff_size];
    boost::outbuf::cstr_writer csw(buff, buff_size);
    auto result = csw.finish();
    if (result.truncated) {
      // ...

Those `finish` functions above do not belong to `outbuf`.
They are defined in the concrete derived types only.
It's solely by convention that they share the same name.

Yes, using `string_maker` still leads to heap allocation
and `cstr_writer` to string truncation. The problem isn't
solved by these. However, a string object is never the final
destination. So the user could rather use another class
that writes the message directly into the final destination
( output console, a log file, an LCD display or whatever ).
It is not difficult to implement concrete subtypes of `outbuf`.

`outbuf` is actually a type alias:

    template <bool NoExcept, typename CharT>
    class basic_outbuf;

    using outbuf = basic_outbuf<false, char>;
    using outbuf_noexcept = basic_outbuf<true, char>;

That `NoExcept` template parameter is perhaps the controversial part.
It is not present originally in my formatting library.

Besides the destructor, `basic_outbuf` has only one virtual
function: `recycle()`, and it is declared as `noexcept(NoExcept)`.
This is the only effect the `NoExcept` template parameter has.
All other functions are guaranteed not to throw.

Hence, by taking a `outbuf_noexcept&` parameter, a function
states that the destination must not throw. That might be
particularly good if such object comes from another module
and we must avoid exceptions crossing modules boundaries.

On the other hand, if a function takes an `outbuf&`, then it also
accepts `outbuf_noexcept&`, because `basic_outbuf<true, CharT>&`
derives from `basic_outbuf<false, CharT>`.

When using `string_maker` you can choose between the two kinds.
`string_maker<true>` derives from `basic_outbuf<true, char>&`.
So if any exception raises from its internal `std::string`,
then it is caught by a try/catch(...) block, stored as
an `exception_ptr` and rethrown by `finish()`. This has the
undesirable effect of delaying its proper handling ( after all,
we rather stop what's being doing as soon as possible when
an error appears ). So I think the recommendation would be to use
`string_maker<false>&` if possible, and `string_maker<true>&`
only if necessary.

The reason why I think it's controversial is that it makes
`recycle()` violates the Lakos Rule. And although the Lakos Rule
is not a requirement in boost (afaik), I was wondering whether
this library could interest LEWG in future as well.
Anyway, this whole noexcept idea is not a central part of this
library and can it be removed.

Now, the other topic is how to implement that `get_message` function,
i.e., how to write into an `outbuf` object.
One can use `puts` and `putc` functions to insert string and chars.
One can also use `fmtlib` through an output iterator adapter.
Or one can write directly in the buffer.
But I will ask you to read the doc [3] for that. It's a quick read,
quicker than this email.

The repository is:

I would prefer it to be part of Boost.Core instead of being
a standalone library, and also to remove the `outbuf`
namespace. But that is up to you.

Best regards,

[1] The Stringify library:

[2] The great performance of Stringify is mainly thanks to
    the design of outbuf ( named there as `output_buffer` ):

[3] "Writing into and outbuf object"

Boost list run by bdawes at, gregod at, cpdaniel at, john at