OK, it's "Answer my own question" time...
First, thanks to KennyTM for the history. As he says, C++ was NOT designed with exceptions from day one, so it's unsurprising that iostreams 'exception' handling was bolted on afterwards.
Second, as Neil B points out, having exceptions on input format conversion errors would be a significant pain. This surprised me, because I was considering iostreams as a simple filesystem wrapper layer, and I hadn't considered that case at all.
Third, it appears BOOST does bring something to the party: Boost.IOStreams. If I understand correctly, these handle the low-level I/O and buffering aspect of streams, leaving the regular c++ IOStreams library to handle conversion issues. Boost.IOStreams does use exceptions in the way I'd expect. If I understand it correctly, Kenny's example could also look like this:
#include <ostream>
#include <boost/iostreams/device/file.hpp>
#include <boost/iostreams/stream.hpp>
int main () {
boost::iostreams::stream_buffer <boost::iostreams::file_source> buf("test.txt");
std::istream file(&buf);
try {
std::string buf;
while (std::getline(file, buf))
std::cout << "Read> " << buf << "\n";
}
catch (std::ios_base::failure::failure e) {
std::cout << "Exception opening/reading file\n";
}
std::cout.flush();
file.close();
return 0;
}
I think with this version, things like "file not found" should throw, but 'istream' errors will be reported by badbit/failbit.