[optional_io] InputStreamable refinement

This is a spin off from the thread "[lexical_cast] A suggestion" While trying to get lexical_cast and optional to play nice I've run up against some problems with the InputStreamable solution for optional types in optional_io.hpp. The crux of the problem is that I think any given optional type should behave as much like the type it wraps as possible. For example I think this: stringstream sin ("1.2.3.4.5"); optional<double> token; while (sin >> token) cout << token << endl; Should have the same output as this: stringstream sin ("1.2.3.4.5"); double token; while (sin >> token) cout << token << endl; And that this shouldn't fail: stringstream sin ("test"); optional<string> token; sin >> token; // token shouldn't be empty The current InputStreamable solution for optional does not pass these tests. But that's not all, I also believe that there is room for even more improvement here! Since it's semantically valid to have an un- initialized optional type, I think failure to extract something off a stream should return an empty optional type _without_ setting the failbit on the stream. Here's a usage example that demonstrates what I mean by that: // output is "1 nan nan nan nan 5.5 nan nan nan nan nan nan nan -3.2 nan " istringstream sin (" 1 this 5.5 is a test -3.2 a"); optional<double> token; const double nan = numeric_limits<double>::quiet_NaN (); while (sin >> token) cout << token.get_value_or (nan) << " "; // output is "1 " istringstream sin (" 1 this 5.5 is a test -3.2 a"); double token; while (sin >> token) cout << token << " "; I did submit a ticket with the final comment proposing code that is (maybe?) a solution to this refinement. ( https://svn.boost.org/trac/boost/ticket/2659 ) Please pardon any guffaws I made in replies to that ticket, I'm still learning about the nuts and bolts of streams and boost as I go. However there is still one problem, and this brings us back to lexical_cast. Consider this example: int an_invalid_uint = -1; unsigned i = lexical_cast<optional<unsigned> > (an_invalid_uint).get_value_or (0); I believe we should expect this to store 0 in i, but instead it will throw a bad_lexical_cast exception even with my proposed revision to optional_io.hpp. A possible solution is a specialization for optional<Target> somewhere in lexical_cast.hpp that would replace code that looks something like this (pseudocode): do lexical_cast if failed to cast: throw exception With something like this: do lexical_cast if failed to cast: return optional<Target>() So is there any interest in this sort of refinement of optional's InputStreamable implementation or lexical_cast? -- Andrew Troschinetz Applied Research Laboratories

Andrew wrote:
... Since it's semantically valid to have an un-initialized optional type, I think failure to extract something off a stream should return an empty optional type _without_ setting the failbit on the stream.
Andrew, sorry for the late reply. optional<T> is about initialized versus uninitialized value, it has nothing to do with stream's state. IMO, the failbit should be set. My vision of optional<T> streaming is based on this quote from documentation: "optional<T> intends to formalize the notion of initialization (or lack of it) allowing a program to test whether an object has been initialized and stating that access to the value of an uninitialized object is undefined behavior." Thus, - after a successful read the stream is in a good state and the value is initialized, - after read failure, the failbit bit is set and the value is uninitialized, - printing of initialized optional<T> value produces the same result as printing T value, - printing of uninitialized value sets the failbit.
However there is still one problem, and this brings us back to lexical_cast. Consider this example: int an_invalid_uint = -1; unsigned i = lexical_cast<optional<unsigned> > (an_invalid_uint).get_value_or (0); ... So is there any interest in this sort of refinement of optional's InputStreamable implementation or lexical_cast?
My streaming proposal makes lexical_cast < optional<T> >(v) equivalent to lexical_cast<T>(v) because it would never return uninitialized value. I can use it as a good excuse for implementing no-throw version of lexical_cast < optional<T> >(v) ;-) Alex

On Feb 25, 2009, at 8:21 AM, Alexander Nasonov wrote:
Andrew, sorry for the late reply.
Don't worry about it, I seem to remember you've been otherwise busy with something just a tad bit more important ;)
optional<T> is about initialized versus uninitialized value, it has nothing to do with stream's state. IMO, the failbit should be set.
My vision of optional<T> streaming is based on this quote from documentation:
"optional<T> intends to formalize the notion of initialization (or lack of it) allowing a program to test whether an object has been initialized and stating that access to the value of an uninitialized object is undefined behavior."
Thus, - after a successful read the stream is in a good state and the value is initialized, - after read failure, the failbit bit is set and the value is uninitialized, - printing of initialized optional<T> value produces the same result as printing T value,
That's what you might expect, but in fact printing optional<T> is not the same as printing T. Printing optional<T> is the same as printing " " + T. An extra space is added before the value of T.
- printing of uninitialized value sets the failbit.
Again, things are not as you might expect. Printing optional<T> when uninitialized shows "--" and does not set the failbit. I find this particularly odd and would certainly prefer either the failbit being set as you say, or for optional<T> to not be OutputStreamable at all.
My streaming proposal makes lexical_cast < optional<T> >(v) equivalent to lexical_cast<T>(v) because it would never return uninitialized value. I can use it as a good excuse for implementing no-throw version of lexical_cast < optional<T> >(v) ;-)
Sorry it's been a while, which proposal where? Wouldn't implementing a no-throw version of lexical_cast for optional<T> necessarily couple lexical_cast and Boost.Optional? In related news, I was briefly contacted by Fernando Cacciola (author of Boost.Optional) who provided me with some insight about why optional_io.hpp is the way it is. I hesitate to post verbatim a private email I received from him on this mailing list, but I can summarize what I learned. Near the first of this month he informed me that optional_io.hpp initially came from the Serialization library and its requirements were mainly directed by that. I had proposed (and still do propose) this change to optional_io.hpp's operator<<(): if (in) { T x; if (in >> x) { v = x; } else { if (in.fail () && !in.eof ()) { in.clear (); in.get (); } v = optional<T> (); } } else { v = optional<T> (); } return in; He agreed that it would work given my requirements, but said he wouldn't dare change it without bringing in Robert Ramey to the discussion, an author of Boost.Serialization and the original author of the code in question. I wrote Robert soon thereafter with my concerns but unfortunately I never heard back from him. Since it's been some time, allow me to make more clear my reasoning for this refinement. Does the following seem a little weird to anyone else? stringstream sin ("test"); optional<string> token; sin >> token; // token remains uninitialized. // But if sin had " test", // then *token would be "test" And this: optional<string> s; cout << s; // shows "--" But... isn't "--" a valid string? Essentially, I think it would be optimal if streaming optional types behaved as much like streaming the types they wrap as possible. And since optional types can be in an uninitialized state, it seems reasonable that failing to extract one from a stream should simply return an uninitialized optional type without setting the failbit on the stream. Think about it this way: Isn't an uninitialized optional<T> a valid optional<T>? And the failbit got set because we attempted to extract *optional<T>::value_type* from the stream and couldn't. Does that mean we failed to extract an optional<T>? What we failed to extract is a T. Except we're returning a valid optional<T> even in this case. I think that means we actually succeeded in extracting an optional<T>! Sure it's uninitialized, but that's ok for an optional type. I would also like to add that I like your idea that "printing of uninitialized value sets the failbit." That seams a very reasonable improvement as well. But the catch is, I have no idea how these changes would impact the Serialization library. Robert, if you're out there, would you care to weigh in? -- Andrew Troschinetz Applied Research Laboratories
participants (2)
-
Alexander Nasonov
-
Andrew Troschinetz