2

I want to find the number of characters that a stream formatting operation would produce without allocating memory from the heap. In C, it can be done with

int nchars = snprintf(NULL, 0, format_string, args...);

How can it be done within the ostream framework in C++?

An implementation with std::ostringstream may allocate memory from the heap:

template <class T>
int find_nchar(const T& value) {
  std::ostringstream os; // may allocate memory from the heap
  os << value;
  return os.str().size(); // may allocate memory from the heap
}

I think I need to make a custom ostream class to achieve this. The custom ostream should respect all the formatting flags one can set for the normal std::ostream.

I am searching for a solution that only uses the C++ standard library, not boost::iostreams, for example.

olq_plo
  • 1,002
  • 10
  • 18

1 Answers1

2

Rather than a custom std::ostream it might be easier -- and perhaps more flexible -- to implement a custom std::streambuf that can then be used with any std::ostream.

#include <streambuf>

template <class CharT, class Traits = std::char_traits<CharT>>
struct counting_streambuf: std::basic_streambuf<CharT, Traits> {
  using base_t = std::basic_streambuf<CharT, Traits>;
  using typename base_t::char_type;
  using typename base_t::int_type;

  std::streamsize count = 0;

  std::streamsize xsputn(const char_type* /* unused */, std::streamsize n)
    override
    {
      count += n;
      return n;
    }

  int_type overflow(int_type ch)
    override
    {
      ++count;
      return ch;
    }

};

Then use as...

#include <iostream>

int
main (int argc, char **argv)
{
  using char_type = decltype(std::cout)::char_type;

  counting_streambuf<char_type> csb;

  /*
   * Associate the counting_streambuf with std::cout whilst
   * retaining a pointer to the original std::streambuf.
   */
  auto *oldbuf = std::cout.rdbuf(&csb);
  std::cout << "Some text goes here...\n";

  /*
   * Restore the original std::streambuf.
   */
  std::cout.rdbuf(oldbuf);
  std::cout << "output length is " << csb.count << " characters\n";
}

Running the above results in...

output length is 23 characters

Edit: The original solution didn't overload overflow. This works on Linux but not on Windows. Thanks go to Peter Dimov from Boost, who found the solution.

olq_plo
  • 1,002
  • 10
  • 18
G.M.
  • 12,232
  • 2
  • 15
  • 18
  • Thank you, this looks very good! Do I also have to override sputn(...) and sputc(...)? – olq_plo Sep 13 '19 at 17:12
  • I don't think you should have to do anything with `sputn` -- it's non-virtual and the default implementation should call the most derived `xsputn` implementation. Likewise `sputc` is not virtual so you have to rely on the base implementation. That should all be fine though as far as I recall. – G.M. Sep 13 '19 at 18:16
  • Related issue (but not a duplicate) https://stackoverflow.com/questions/41377045/is-there-a-simple-way-to-get-the-number-of-characters-printed-in-c – olq_plo Oct 14 '19 at 12:54
  • This code fails on Windows with msvc. It looks like xsputn is never called. – olq_plo Oct 14 '19 at 13:53
  • Does adding an `<< std::flush` to the end of the output sequence help? I was fairly sure `xsputn` would be called at some point. If flushing doesn't help I can have another look at some other code I have. – G.M. Oct 14 '19 at 16:55
  • I tried `<< std::flush`, but it does not work, if you have some other idea, I would really appreciate it. I am very surprised that this does not work. According to the docs of streambuf it should work. – olq_plo Oct 15 '19 at 06:49
  • @olq_plo Updated the post to provide an alternative implementation. – G.M. Oct 15 '19 at 13:31
  • Thank you for the edit, this looks good. I have a suspicion that the base implementation of basic_streambuf on Windows may set the error state and xsputn is never called. – olq_plo Oct 15 '19 at 15:40
  • Looking closer at your new code, it should make no difference. Only the virtual methods are called by the ostream, and in your implementations you just call the methods of the base, which should happen anyway when you don't overload the methods. Still, for debugging the issue, as you say, it will be useful to implement all the virtual methods. – olq_plo Oct 15 '19 at 15:45
  • Peter Dimov from Boost figured it out, see https://cpplang.slack.com/archives/C27KZLB0X/p1571154902263300. It is necessary to overload `overflow` as well. Do you want to update your answer or should I propose an edit? – olq_plo Oct 16 '19 at 22:02
  • I'm a bit confused. The new implementation I proposed in the edit *does* override `overflow` (I can't check the link you provided as I don't have an account on that site). – G.M. Oct 17 '19 at 03:32
  • Yes, but you call the base version, which triggers an error state on the stream when called. Please read the discussion with Peter on cpplang why overflow has to be changed. Finally, it is not necessary to overload all the other methods. – olq_plo Oct 17 '19 at 05:35
  • Sorry, but there seems to be some confusion. My edit added *two* new implementations. The first of those overrides `overflow` (but *doesn't* call the base version) and is intended as a fix for the problem under discussion. The second implementation in the edit (the one to which I think you are referring) was intended purely for diagnostic purposes in the event that no other fix could be found. Having said that, it sounds as if you have a working solution anyway -- so I'll leave it at that :-) – G.M. Oct 17 '19 at 07:52
  • I am going to propose an edit then, because the working solution is very concise. – olq_plo Oct 17 '19 at 07:57