5
\$\begingroup\$

I got tired of the boilerplate and tedium restoring formatting context, so I made an RAII stasher that relies on the destroy-at-end-of-full-statement temporary semantics. With C++17 I can get it down to a single type, no helpers:

#include <iostream>
template< template<typename, class> class streamtmp, typename charT, class traits >
struct fmtstash {
    typedef streamtmp<charT,traits> streamtype;
    streamtype &from;
    std::ios_base::fmtflags flags;
    std::streamsize width;
    std::streamsize precision;
    charT fill;
    fmtstash(streamtype &str)
        : from(str), flags(str.flags()), width(str.width())
        , precision(str.precision()), fill(str.fill())  {}
    ~fmtstash() { from.flags(flags), from.width(width), from.precision(precision), from.fill(fill); }
    template<typename T> streamtype  &operator<<(const T &rhs) { return from << rhs; }
    template<typename T> streamtype  &operator>>(      T &rhs) { return from >> rhs; }
};

int main(int c, char **v)
{
    using namespace std;
    fmtstash(cout) << hex << 51901 <<'\n';
    cout << 51901 <<'\n';

    return 0;
}

which produces about the easiest-to-write-and-read formatting I can manage.

Am I missing any bets here? Shorter or more robust is what I'm after.

The biggest improvement I found since I first wrote it is to use from << rhs instead of the from.operator<<(rhs) I had, since user-defined stream insert/extractors have to be free functions and of course an explicit member callout breaks those.

I could make the template args simpler, just template<class streamtmp>, the full breakout is a holdover from when I had the internal reference as ios_base<charT,traits> &from; instead of the full original type, but I ran across a broken inserter that wanted to be first or only and took an ostream reference as its left operand.

And of course the boost savers don't supply the inserter and extractor templates at all. Is this an oversight or is there some argument against using the destroy-at-end-of-full-expression semantics this way?

\$\endgroup\$
2
  • 1
    \$\begingroup\$ I posted something similar a couple of years ago, so you might want to compare approaches. I do like the simple usability of your code; I wish I'd thought of making it usable as a stream itself. \$\endgroup\$ Commented Mar 9, 2020 at 9:24
  • \$\begingroup\$ @TobySpeight I missed yours in searching for dups (iostream vs stream, my bad), so I missed all the great comments and discussion there. Locales and afaics never locally changed, and changing them seems so expensive, I left them out. Also seems I should be using && not &, to get the value-or-ref deduction working. \$\endgroup\$
    – jthill
    Commented Mar 9, 2020 at 15:40

2 Answers 2

3
\$\begingroup\$

One tiny improvement I'd suggest is that the stored state can all be declared const:

const std::ios_base::fmtflags flags;
const std::streamsize width;
const std::streamsize precision;
const charT fill;

There are no implications for assignability of fmtstash objects, as we already had a reference member.

\$\endgroup\$
2
\$\begingroup\$

Since you aren't using the full template form of the stream, just drop it. charT can be replaced by stream::char_type.

There is an rvalue overload for standard stream types.

The parameters to main are conventionally named argc and argv. Since you aren't using these parameters, omit them to avoid unused parameter warnings.

I think the code may be more readable if you avoid squeezing everything on one line:

~fmtstash()
{
    from.flags(flags);
    from.width(width);
    from.precision(precision);
    from.fill(fill);
}

instead of

~fmtstash() { from.flags(flags), from.width(width), from.precision(precision), from.fill(fill); }

Similarly,

template <typename T>
streamtype& operator<<(const T& rhs)
{
    return form << rhs;
}

instead of

template<typename T> streamtype  &operator<<(const T &rhs) { return from << rhs; }

And of course the boost savers don't supply the inserter and extractor templates at all. Is this an oversight or is there some argument against using the destroy-at-end-of-full-expression semantics this way?

Probably because the "destroy-at-end-of-full-expression semantics" is not very useful — boost savers work with scopes, which allow for more flexible state saving. If you really want the full expression semantics, you can do it like this:

boost::io::ios_flags_saver(std::cout),
std::cout << std::hex << 51901 <<'\n';
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.