2535. Inconsistency between ostream::write and ostream::operator<<

Section: 27.4.4.4 [string.io], 31.7.6.4 [ostream.unformatted] Status: NAD Submitter: Marshall Clow Opened: 2015-09-10 Last modified: 2016-07-13

Priority: 2

View all other issues in [string.io].

View all issues with NAD status.

Discussion:

Consider the following program:

#include <iostream>
#include <ostream>
#include <string>

template <class CharT>
class testbuf : public std::basic_streambuf<CharT> 
{
public:
  testbuf() {}

protected:
  virtual std::streamsize xsputn(const CharT *s, std::streamsize n)
  {
    std::cout << "xsputn('" << s << "', " << n << ")\n";
    return n;
  }
};

int main () 
{
  testbuf<char> sb;
  std::ostream os (&sb);
  
  std::string s1{"abc"};
  os.write(s1.data(), s1.size());
  
  os.write(s1.data(), 0);
  
  std::string s2{"def"};
  os << s2;
  
  std::string s3{""};
  os << s3;
  
  os << "";
}

What should it print?

libc++:

xsputn('abc', 3)
xsputn('def', 3)

libstdc++:

xsputn('abc', 3)
xsputn('abc', 0)
xsputn('def', 3)
xsputn('', 0)
xsputn('', 0)

VS:

xsputn('abc', 3)
xsputn('def', 3)
xsputn('', 0)
xsputn('', 0)

27.4.4.4 [string.io]/5 seems to say that an implementation is required to call sputn (which calls xsputn) even if there's nothing to output (in the case of ostream::operator<<(basic_string)).

31.7.6.4 [ostream.unformatted]/5.1 implies that it's OK to not call sputn if there's nothing to output (in the case of ostream::write)

Backstory: A user has a ostream with a subclass of basic_streambuf. it creates an output file on first write. Sometimes, he calls ostream::write(p, 0), and expects this to create the file. This doesn't work in libc++, and then he pointed out the inconsistency between operator<< and write.

For reference to a bug report see here.

There are two obvious possible resolutions:

  1. a) require all output functions to call sputn, even if there are no characters to output. In practice, this reduces to "string-like" things which are empty (string, string_view, char*, etc), and write(ptr, len).

  2. b) remove the requirement that ostream::operator<< call sputn when there are no characters to output.

[06-2016 Oulu, Saturday morning]

MC: Problem is this program produces different outputs on different platforms. The issue is what happens when you write 0 bytes: do you have to call xsputn? This affects a real customer.

NJ: Why is this a problem? Why not QOI?

DKu: I don’t think it’s a problem.

MC: Making your own streambuf is an explicit customization point.

DKu: But you should expect different numbers of calls.

NJ: They may even split the input, and call xsputn more than once for an input.

MC: Do we actually say that anywhere?

DKu: I think so.

MC: If you can find that, I’d be OK with NAD. The other thing that bothers me is that in one case it says you’re required to call sputn even if there’s no input, but the other wording doesn’t contain that requirement.

DKu: The first wording says “as if by”, which may give wiggle room to not call it.

DKu: In the second wording, sputc will never call sputn; it puts character into buffer, and calls overflow if the buffer is full. sputn is strictly an optimization.

MC: OK, I’m convinced this could be NAD, that the standard simply gives no guarantees about this. Are we OK with this lack of precision and implementation variance, or does the spec need to be more precise?

DKu: If you look at [ostream]/p2, it deliberately doesn’t specify how the functions are called. Even if sputn is called, no guarantee that xsputn is called at all: if there’s space in the buffer, the implementation may just put the characters in the buffer. This flexibility makes user implementations nicer, so I think this is definitely NAD

BD: This stuff is incredibly chewed-over. There used to always be a group working on this stuff; it’s hard to believe there’s anything in here that’s not deliberate, so you can’t change it with this small group; you need to talk to all the implementers.

MC: Any objections to NAD?

no objections.

Closing as NAD.

Proposed resolution: