C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Format output-streamable types?

From: Tiago Freire <tmiguelf_at_[hidden]>
Date: Sat, 14 Dec 2024 16:00:46 +0000
It isn't, I was comparing it to a lazy_format.

That although lazy_format is not a bad idea in absence of anything, it's not as good as the ability to holistically introspect about the arguments instead of doing them 1 by 1.
The point being there's not much to be gained to try and salvage std::ostream, it's design just leaves too much on the floor compared to what you can do with alternative designs, and it's for the best to stop supporting it and let it go the way of the dodo.


________________________________
From: Std-Proposals <std-proposals-bounces_at_lists.isocpp.org> on behalf of Robin Savonen Söderholm via Std-Proposals <std-proposals_at_[hidden]>
Sent: Saturday, December 14, 2024 1:22:06 PM
To: std-proposals_at_lists.isocpp.org <std-proposals_at_[hidden]>
Cc: Robin Savonen Söderholm <robinsavonensoderholm_at_[hidden]>
Subject: Re: [std-proposals] Format output-streamable types?

How is `print_to_sink` different from `format_to(ostreambuf_iterator<char>(cout), <format string>, ...<arguments>)`? More than verbosity of course..

I hacked together some implementations here: (godbolt): https://godbolt.org/z/G44dEabjq and (github): doocman/dtl: Doocman Template Library (C++)<https://github.com/doocman/dtl> .
There are probably details about these that could be improved, but my hope is that the overall behaviour is fine, but I'll gladly take some feedback while trying to write out a proper proposal.

// Robin

On Fri, Dec 13, 2024 at 10:05 PM Tiago Freire <tmiguelf_at_[hidden]<mailto:tmiguelf_at_[hidden]>> wrote:
The lazy_format, although not entirely a bad idea what you gain from it is mostly peanuts.
Because of the way the operator << mechanism works the implementation has to resolve each of the segments (i.e. << “segment one” << “segment two” << etc..) one by one and cannot optimize with the fact that there are more segments following, this despite the fact that all segments will be available in the stack before the last “<<” in the chain completes.
A much more efficient way to handle this is to completely forgo the usage of any ostream operator and just pass the ostream object as one of the arguments, like so:
print_to_sink(std::cout, "formatting string", …arguments); //pass arguments as a reference is better.

Which can be made more generic as:
print_to_sink<underlying_encoding>(<generic_sink>, <format string>, …<arguments>);

This allows for the function to introspect about all arguments and be much more efficient (and I’m talking easily a couple of orders of magnitude faster, not just a mere 10%).

Take it for what it is, it is an opinion, my opinion is that std::ostream is obsolete and should eventually be deprecated and removed, it’s a waste of time to try and upgrade it.



From: Std-Proposals <std-proposals-bounces_at_[hidden]cpp.org<mailto:std-proposals-bounces_at_[hidden]>> On Behalf Of Robin Savonen Söderholm via Std-Proposals
Sent: Friday, December 13, 2024 2:14 PM
To: std-proposals_at_[hidden]<mailto:std-proposals_at_[hidden]>
Cc: Robin Savonen Söderholm <robinsavonensoderholm_at_gmail.com<mailto:robinsavonensoderholm_at_[hidden]>>
Subject: Re: [std-proposals] Format output-streamable types?


Ok, taking feedback from you both I propose this:

- Add even more specialisations to std::formatter (one paper)

- Look at a way to let users delegate the format (output stream) to the output stream (formatter).
The first one feels somewhat trivial, and I do think that the standard should have a formatter specialisation for all types that already provide an operator << to an ostream.
The second one may need some design, I'll try to work it out during the weekend. At the top of my head I was thinking about something like this:
To format <<:
```c++
std::print("foo only provides operator<< but here I print it anyway: {}", stream_formatter(foo));
```
The other one I think could be nicely solved by another construct that I have been wanting to implement: lazy_formatted_string and lazy_format, so that we could write something like this:
```c++
std::cout << "bar only supports the modern formatting facilities but here I let a lazy_formatted_string to efficiently stream to cout without intermediate allocations " << lazy_format("{}", bar);
```.
My main concern with these API:s are wheter they should capture by value, by reference or some automatic version of either (value for trivial types and for r-value inputs, reference for non-trivial lvalue inputs for example). I also like the idea of the lazy_format to allow some optimisations in the std::string::operator= department:
```cpp
std::string str = ...;
// and some while later:
// same as str.clear(); std::format_to(std::backinserter(str), "...", ...);
str = lazy_format("....", ...);
```
but these lazy-evaluation stuff may lead to life-time issues for careless users (although we already have the same issue with std::span and std::string_view).

Anyway, if no objections are made I'll look into some implementations later and hopefully come back with a more fleshed-out proposal. Thanks!

PS: rather than having the magic ` formatter_uses_ostream_insertion `-variable (which I'd refrain from just for the sake of potential ambiguous/conflicting specialisations), I think it would make more sense to have a template-base class that you may use for your formatter:

```cpp
namespace std {
template <typename Char>
struct formatter<my_namespace::foo, Char> : ostream_formatter<my_namespace::foo, Char> {};
}
```
.
I prefer it because it feels slightly more explicit.
DS.

// Robin

On Thu, Dec 12, 2024, 22:21 Jonathan Wakely <cxx_at_[hidden]<mailto:cxx_at_[hidden]>> wrote:


On Thu, 12 Dec 2024 at 20:24, Robin Savonen Söderholm via Std-Proposals <std-proposals_at_[hidden]<mailto:std-proposals_at_[hidden]>> wrote:
Ok, so just a proposal to add more formatter specialisations?

See https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1636r2.pdf

Formatters for thread::id and filesystem::path have now been added, the rest haven't.


Users wanting "auto conversions" would have to implement them themselves.

I like the idea of some kind of reuse between operator<< and std::formatter, maybe opt-in. I see no reason why we would ever want to completely remove basic_ostream.

We could have a variable template like:

template<typename T>
  constexpr bool formatter_uses_ostream_insertion = false;

and then define a formatter specialization that only supports empty format specs and uses operator<< to produce a string/wstring:

template<typename T>
requires formatter_uses_ostream_insertion<T>
class formatter<T> // ...

We could also provide the reverse opt-in, so that ostream insertion uses std::format (or std::format_to) to write to the ostream.

And we could provide a generic std::to_string(T) function which uses std::format if the type is formattable, and operator<< if that works (and is ill-formed otherwise).

There's lots of room for improvement in this space.



Received on 2024-12-14 16:00:50