[Range] New I/O functions for ranges
I'm currently putting together a proposal to the standards committee
about a set of range I/O functions, but i think they would make a good
addition to Boost as well. They would probably fit in the Range
library - i don't think they warrant a library all of their own.
The stuff i wrote for the standards proposal is available at
http://cpp.indi.frih.net/rangeio/ - it goes into quite a bit of detail
about the interface, and links to a sample implementation. But here's
the executive summary:
There is a single output function - currently called "write_all" -
that takes a range and, optionally, a delimiter, like this:
// BEGIN CODE /////////////////////////
auto const r = std::array
On 9/27/2014 5:21 PM, Indiana wrote:
I'm currently putting together a proposal to the standards committee about a set of range I/O functions, but i think they would make a good addition to Boost as well. They would probably fit in the Range library - i don't think they warrant a library all of their own.
The stuff i wrote for the standards proposal is available at http://cpp.indi.frih.net/rangeio/ - it goes into quite a bit of detail about the interface, and links to a sample implementation. But here's the executive summary:
There is a single output function - currently called "write_all" - that takes a range and, optionally, a delimiter, like this:
// BEGIN CODE ///////////////////////// auto const r = std::array
{1, 2, 3, 4}; cout << write_all(r); // prints "1234" cout << "{ " << write_all(r, ", ") << " }"; // prints "{ 1, 2, 3, 4 }" // END CODE ///////////////////////////
It takes anything that works with range-for, so it works with range adaptors:
// BEGIN CODE ///////////////////////// struct is_even { auto operator()(int x) const -> bool { return x % 2 == 0; } };
cout << write_all(r | reversed | filtered(is_even{})); // prints "42" // END CODE ///////////////////////////
Note also that the delimiter doesn't have to be a string (there are some examples in the reference of that).
Why not just use std::copy() and stream iterators? Several reasons - the most obvious of which you can see above: this syntax is much cleaner, much less error-prone, well-integrated with the existing stream I/O mechanisms, and it produces properly delimited output (unlike the delimited version of ostream_iterator).
But also, stream formatting is properly handled:
// BEGIN CODE ///////////////////////// auto const r = std::array
{1, 2, 3}; std::cout.width(3); std::cout.fill('0');
// --------------------------- // The old way: using std::begin; using std::end; std::copy(begin(r), end(r), std::ostream_iterator<int>{std::cout, ", "}); // even with Boost.Range, this just reduces to: //boost::copy(r, std::ostream_iterator<int>{std::cout, ", "});
// prints: "001, 2, 3, "
// --------------------------- // The new way: std::cout << write_all(r, ", ");
// prints "001, 002, 003" // END CODE ///////////////////////////
And of course this works as expected in chained expressions:
// BEGIN CODE ///////////////////////// auto const r = array
{1, 2, 3}; cout.width(3); cout.fill('0');
cout << '(' << setw(3) << setfill('0') << write_all(r, ',') << ')';
// prints: "(001,002,003)" // END CODE ///////////////////////////
It is also possible to get more information about errors, and react better to them. If there is an error in the formatting or in the stream during an output operation using std::copy() and ostream_iterator, the algorithm will chug merrily along until it's all the way through the range even though it's no longer writing anything. You will have no way to detect where the problem occurred - did it fail after the first element was written, or *before*, or did it fail after the millionth element? If the range is begin lazy-generated, that could be particularly wasteful, or even problematic.
By contrast, write_all() stops immediately when there is an error in the output stream. It is even possible get more information about the last range write operation by capturing the temporary object, like this:
// BEGIN CODE ///////////////////////// auto const r = array
{1, 2, 3}; auto p = write_all(r); cout << p;
if (!cout) { cerr << "only " << p.count << " elements written\n"; cerr << "next element would have been " << *p.next << '\n'; } // END CODE ///////////////////////////
On the input side, there are 7 functions: * overwrite * back_insert * front_insert * insert * back_insert_n * front_insert_n * insert_n
Overwrite simply replaces the contents of a range with whatever is read from input:
// BEGIN CODE ///////////////////////// auto r = array
{}; cin >> overwrite(r); // reads up to 100 ints // END CODE ///////////////////////////
The *insert functions map to the *insert_iterators in the standard library, using push_back(), push_front(), or insert(). In each case:
in >> ???insert(r[, i]);
is basically equivalent to:
copy(istream_iterator
{in}, // use braces on the next line, or face the most vexing parse istream_iterator {}, ???inserter(r[, i])); The *insert_n functions are for the common case where you know in advance how many elements will be read, at maximum - or for when you want to limit the amount of elements read in a single read operation. This:
in >> ???insert_n(r[, i], n);
is conceptually equivalent to:
copy_n(istream_iterator
{in}, n, ???inserter(r[, i])); except it can handle formatting and errors properly.
The reference at http://cpp.indi.frih.net/rangeio/ goes into much more detail, of course.
Why not "write" instead of "write_all" ? How do I "write_all" a range to some other i/o output type( files, strings etc. ) ?
On 14-09-27 09:24 PM, Edward Diener wrote:
Why not "write" instead of "write_all" ?
I'm not great with coming up with names, but I figured write() is not descriptive enough. The key reason I thought "write" alone would not do is because strings are ranges, and writing a string to an output stream means something very different than writing all (or each) of the characters in that string in turn: // BEGIN CODE ///////////////////////// auto str = std::string{"abc"}; // What do you expect on reading: cout << write(str, 'x'); // versus cout << write_all(str, 'x'); // Personally, for the former I expect "abcx", not "axbxc". // END CODE /////////////////////////// The names I considered were things like "write_range()", "print_range()", "stream_range()", "write_each()", "out_all()", "all_of()", "each_of()", and so on. Basically, I wanted to drive the point home that each element in the range is being written, in turn and in order, without being too wordy. If anyone wants to suggest better names for any of the functions, go for it.
How do I "write_all" a range to some other i/o output type( files, strings etc. ) ?
It's the same no matter what you're reading/writing from: // BEGIN CODE ///////////////////////// auto const r = vector<double>{1.0, 2.0, 3.0}; // to the console: cout << write_all(r, ','); // to a file: ofstream outfile{"file.out"}; outfile << write_all(r, ','); // to a socket: // assume io_service, endpoint, acceptor, and error_code are set up boost::asio::ip::tcp::ostream tcpos; acceptor.accept(*tcpos.rdbuf(), error_code); tcpos << write_all(r, ','); // to a string: ostringstream oss{}; oss << write_all(r, ','); // even to a wide-character stream: wcout << write_all(r, ','); // (works because all streams support op<< for char and char*; // wouldn't work if the delimiter were a std::string - it would // need to be a std::wstring in this case) // END CODE /////////////////////////// And the same is true for all the input functions and any input stream type.
-----Original Message----- From: Boost [mailto:boost-bounces@lists.boost.org] On Behalf Of Indiana Sent: 27 September 2014 22:22 To: boost@lists.boost.org Subject: [boost] [Range] New I/O functions for ranges
I'm currently putting together a proposal to the standards committee about a set of range I/O functions, but i think they would make a good addition to Boost as well. They would probably fit in the Range library - i don't think they warrant a library all of their own.
The stuff i wrote for the standards proposal is available at http://cpp.indi.frih.net/rangeio/ - it goes into quite a bit of detail about the interface, and links to a sample implementation. But here's the executive summary:
There is a single output function - currently called "write_all" - that takes a range and, optionally, a delimiter, like this:
<snip> Though I haven't studied it in detail it is interesting and undoubtedly useful. It is certainly absurdly difficult to output containers in C++ (and something that must put lots of people off using it). I suspect the trouble is that people can't agree on what is needed because there are so many possible options. I wonder if you have studied the examples of Steven Watanbe's type erasure library? http://www.boost.org/doc/libs/1_55_0/doc/html/boost_typeerasure.html I found this useful, but it didn't cover all the options that I wanted, so I added a prefix string and a suffix string to the existing delimiter string. I would urge you to consider this as it increases the possible use-cases covered considerably. In particular, it is seems desirable to be able to output values in the C++ syntax for the compiler to read back in? For your amusement, I attached some of hoops that I made 'printers' derived from Steven Watanbe's type erasure abstract sequence printer jump through. Good luck. Paul --- Paul A. Bristow Prizet Farmhouse Kendal UK LA8 8AB +44 (0) 1539 561830
On 14-09-29 01:24 PM, Paul A. Bristow wrote:
I wonder if you have studied the examples of Steven Watanbe's type erasure library?
http://www.boost.org/doc/libs/1_55_0/doc/html/boost_typeerasure.html
I found this useful, but it didn't cover all the options that I wanted, so I added a prefix string and a suffix string to the existing delimiter string.
I would urge you to consider this as it increases the possible use-cases covered considerably. Well, the range I/O functions i'm proposing are not intended to cover every imaginable I/O situation. The goal is just to make the simplest cases - which cover the *VAST* majority of usage - simple to do. Trying to cover things that are very complex, very rare, or that would require a ton of customization options (like automatic line wrapping) would add too much complexity for too little payoff. But that's not really a cop out, because the truth is that the *VAST* majority of things you might want to do are already covered.
In particular, it is seems desirable to be able to output values in the C++ syntax for the compiler to read back in? You mean serialization? There's already a Boost library for that, and anyway that's not the problem domain i'm tackling; i'm dealing with generalized I/O. Whether or not data can round trip depends on the
To demonstrate that, i took the code you pasted, and reimplemented it using write_all(). I tried to match the output exactly, and to keep the code in main() changed as little as possible except for the parts pertaining to output. Of the 19 output operations in the original code: * 13 could be done with just a call to write_all() and no additional code; * 5 could be done with (the same, reused) ~30 lines of additional code to create a smart delimiter (these were the ones that print the range in columns in row-major order); * 1 required ~100 lines of additional code to create a smart delimiter and range adaptor (this was the one that prints the range in column-major order; because it prints the range out of order, it requires a custom range adaptor to do the reordering).* You can see the code i wrote here: http://pastebin.com/R1SwvBEr . In total the whole program came to ~300 lines (compared to the original at ~480 lines, around at least 40% less code). Not only that, every output statement handles formatting correctly. (Compare doing "std::cout << std::setw(4) << std::setfill('0'); separator_printer p1(","); p1.print(std::cout, test); std::cout << '\n';" to doing "std::cout << std::setw(4) << std::setfill('0') << write_all(test, ",") << '\n';". The former will print "0001,2,3,4,...", the latter will do "0001,0002,0003,0004,...".) And they also correctly handle I/O errors. What I'm trying to demonstrate is: all the complexity you *think* you need in range I/O functions... you probably don't. You will probably never find a case where what you need in range output cannot be handled by a simple function that just prints the range elements in order, possibly with a delimiter between. Anything else - like reordering the elements, skipping elements conditionally, etc. - is outside that scope; that is the domain of (for example) range adaptors (because you're adapting the range). Combining range adaptors, smart delimiters, and write_all() will cover just about every range output situation you can conceive of. So if we can get printing the elements of a range in order - possibly with a delimiter - done *RIGHT* (ie, doing formatting, error handling, etc. right) and *EASY*, then we will have covered a hell of a lot of ground. That is the philosophy i had in mind, not trying to design an interface that can do *everything*. (*Incidentally, maybe there *should* be a column-major reordering range adaptor added to Boost. Or rather, a "transpose" adaptor that takes a forward range that is presumed to be a MxN matrix in row major format, and returns a view of it transposed. I can see that as being generally useful.) formatting options you choose when doing input and output, and the way you've written your class's stream inserter and extractor. For example, if you print a number in a German or French locale then try to read it into a US locale, it's probably not going to work. If you pick the right options, round tripping is certainly possible.
-----Original Message----- From: Boost [mailto:boost-bounces@lists.boost.org] On Behalf Of Indiana Sent: 30 September 2014 21:53 To: boost@lists.boost.org Subject: Re: [boost] [Range] New I/O functions for ranges
I wonder if you have studied the examples of Steven Watanbe's type erasure library?
http://www.boost.org/doc/libs/1_55_0/doc/html/boost_typeerasure.html
I found this useful, but it didn't cover all the options that I wanted, so I added a prefix string and a suffix string to the existing delimiter string.
I would urge you to consider this as it increases the possible use-cases covered considerably. Well, the range I/O functions i'm proposing are not intended to cover every imaginable I/O situation. The goal is just to make the simplest cases - which cover
On 14-09-29 01:24 PM, Paul A. Bristow wrote: the *VAST* majority of usage - simple to do. Trying to cover things that are very complex, very rare, or that would require a ton of customization options (like automatic line wrapping) would add too much complexity for too little payoff. But that's not really a cop out, because the truth is that the *VAST* majority of
you might want to do are already covered.
To demonstrate that, i took the code you pasted, and reimplemented it using write_all(). I tried to match the output exactly, and to keep the code in
changed as little as possible except for the parts pertaining to output.
Of the 19 output operations in the original code: * 13 could be done with just a call to write_all() and no additional code; * 5 could be done with (the same, reused) ~30 lines of additional code to create a smart delimiter (these were the ones that print the range in columns in row-major order); * 1 required ~100 lines of additional code to create a smart delimiter and range adaptor (this was the one that prints the range in column-major order; because it prints the range out of order, it requires a custom range adaptor to do the reordering).*
You can see the code i wrote here: http://pastebin.com/R1SwvBEr . In total the whole program came to ~300 lines (compared to the original at ~480 lines, around at least 40% less code). Not only that, every output statement handles formatting correctly. (Compare doing "std::cout << std::setw(4) << std::setfill('0'); separator_printer p1(",");
std::cout << '\n';" to doing "std::cout << std::setw(4) << std::setfill('0') << write_all(test, ",") << '\n';". The
things main() p1.print(std::cout, test); former will print
"0001,2,3,4,...", the latter will do "0001,0002,0003,0004,...".) And they also correctly handle I/O errors.
What I'm trying to demonstrate is: all the complexity you *think* you need in range I/O functions... you probably don't. You will probably never find a case where what you need in range output cannot be handled by a simple function that just
range elements in order, possibly with a delimiter between. Anything else -
reordering the elements, skipping elements conditionally, etc. - is outside
that is the domain of (for example) range adaptors (because you're adapting
range). Combining range adaptors, smart delimiters, and write_all() will cover just about every range output situation you can conceive of.
So if we can get printing the elements of a range in order - possibly with a delimiter - done *RIGHT* (ie, doing formatting, error handling, etc. right) and *EASY*,
Neat :-) The measure example 45210.(+/-1234.0) m, 789.00(+/-2.5000) m, 0.00056700(+/-2.3400e-05) m - with uncertainty and units is particularly nice (and testing). (I think I remember that there was a mistake in the example that fails to save the IO settings for uncertainty). prints the like that scope; the then we
will have covered a hell of a lot of ground. That is the philosophy i had in mind, not trying to design an interface that can do *everything*.
You may well be right, but I'm only reporting what I found were my 'needs'. You can show how to wrap write_all output (with 'smart' delimiter - no ugly trailing commas!) with prefix and suffix strings, that will probably be fine.
In particular, it is seems desirable to be able to output values in the C++ syntax for the compiler to read back in?
(*Incidentally, maybe there *should* be a column-major reordering range adaptor added to Boost. Or rather, a "transpose" adaptor that takes a forward range
I only meant a string in C++ syntax like int[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; that you have shown is possible. that is
presumed to be a MxN matrix in row major format, and returns a view of it transposed. I can see that as being generally useful.)
Now you need some enthusiastic users to support you on a review ;-) Paul --- Paul A. Bristow Prizet Farmhouse Kendal UK LA8 8AB +44 (0) 1539 561830
On 14-10-01 05:00 AM, Paul A. Bristow wrote:
You may well be right, but I'm only reporting what I found were my 'needs'.
You can show how to wrap write_all output (with 'smart' delimiter - no ugly trailing commas!) with prefix and suffix strings, that will probably be fine. Yeah, sure - actually write_all() does that right out of the box:
o << "prefix" << write_all({1,2,3}, "delimiter") << "suffix"; // Prints: "prefix1delimiter2delimiter3suffix"; So: o << '{' << write_all({1,2,3}, ',') << '}'; // Prints: "{1,2,3}"
I only meant a string in C++ syntax like
int[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
that you have shown is possible. Actually, you can probably generalize that. The following code isn't tested, but:
template
Now you need some enthusiastic users to support you on a review ;-) Yeah? I'm not sure how the process goes here. Do I make a pull request on the Boost.Range repository and implement the changes? Or do I implement the idea separately, so people can experiment and mess around with it, and only after it's passed review try to integrate it with the rest of the library? Do I get in touch with the Boost.Range maintainers (seems to be Thorsten Ottosen and Neil Groves)?
-----Original Message----- From: Boost [mailto:boost-bounces@lists.boost.org] On Behalf Of Indiana Sent: 05 October 2014 16:47 To: boost@lists.boost.org Subject: Re: [boost] [Range] New I/O functions for ranges
On 14-10-01 05:00 AM, Paul A. Bristow wrote:
You may well be right, but I'm only reporting what I found were my 'needs'.
You can show how to wrap write_all output (with 'smart' delimiter - no ugly trailing commas!) with prefix and suffix strings, that will probably be fine. Yeah, sure - actually write_all() does that right out of the box:
o << "prefix" << write_all({1,2,3}, "delimiter") << "suffix"; // Prints: "prefix1delimiter2delimiter3suffix";
So:
o << '{' << write_all({1,2,3}, ',') << '}'; // Prints: "{1,2,3}"
I only meant a string in C++ syntax like
int[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
that you have shown is possible. Actually, you can probably generalize that. The following code isn't tested, but:
template
void format_range(std::basic_ostream & o, Range&& r) { using std::begin; using std::end; using value_type = std::decay_t ; std::basic_ostringstream
oss{}; oss.imbue(std::locale::classic()); oss << boost::typeindex::type_id
().pretty_name(); oss << '[' << std::distance(begin(r), end(r)) << ']' << " = "; oss << '{' << write_all(std::forward<Range>(r), ", ") << "};\n"; o << oss.str(); }
Calling format_range() for any range type should give the output you want, more or less, dependent on the range value type.
auto v = std::vector<int>{1,2,3}; cout << format_range(v); // Prints: "int[3] = {1, 2, 3};" auto const a = std::array
{1.1, 2.2, 3.3, 4.4}; cout <<
format_range(a); //
Prints: "double[4] = {1.1, 2.2, 3.3, 4.4};"
Even neater :-)
Now you need some enthusiastic users to support you on a review ;-) Yeah? I'm not sure how the process goes here.
Nor me. I assume you've seen http://www.boost.org/development/requirements.html
Do I make a pull request on the Boost.Range repository and implement the changes? Or do I implement the idea separately, so people can experiment and mess around with it, and only after it's passed review try to integrate it with the rest of the library? Do I get in touch with the Boost.Range maintainers (seems to be Thorsten Ottosen and Neil Groves)?
I doubt if you should try to add to the existing range library - unless the Boost.Range maintainers are keen, but yes - do ask. You could produce a new-name (range_write, range_out, range_IO?) sub-module at GIThub that people can clone into their existing Boost GIT files to see if they like it? Good luck! Paul --- Paul A. Bristow Prizet Farmhouse Kendal UK LA8 8AB +44 (0) 1539 561830
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
On October 6, 2014 5:56:44 AM EDT, "Paul A. Bristow"
-----Original Message----- From: Boost [mailto:boost-bounces@lists.boost.org] On Behalf Of Indiana
I'm not sure how the process goes here.
Nor me.
I assume you've seen
http://www.boost.org/development/requirements.html
Do I make a pull request on the Boost.Range repository and implement the changes? Or do I implement the idea separately, so people can experiment and mess around with it, and only after it's passed review try to integrate it with the rest of the library? Do I get in touch with the Boost.Range maintainers (seems to be Thorsten Ottosen and Neil Groves)?
I doubt if you should try to add to the existing range library - unless the Boost.Range maintainers are keen, but yes - do ask.
You could produce a new-name (range_write, range_out, range_IO?) sub-module at GIThub that people can clone into their existing Boost GIT files to see if they like it?
Consider Robert's blincubator.com, too. ___ Rob (Sent from my portable computation engine)
As an update of what I've decided on doing: Following the advice of Paul
Bristow and Rob Stewart, I have decided to implement the range I/O stuff
as a new library with Boost.Range as a dependency. Once it's all up and
running and tested and such, I can ask Boost.Range's maintainers whether
they'd like to absorb it into Boost.Range or leave it as a separate
library (Boost.RangeIO?). Since the whole thing is already (more or
less) implemented as a standards proposal, all I had to do, really, was
just change the namespace from std to boost, and some other housekeeping
stuff. However, I decided to take the opportunity to examine a
possibility that a couple people have asked about: whether or not these
C++11 facilities can be made available to C++98 code. Looking into that
possibility is what's kept me occupied recently.
That's where I hit a little snag.
It turns out is probably *possible* to implement the facilities in
C++98... however, it is impossible to do so in a way that guarantees
safety. The problem is that C++98 has no way to prevent you from passing
a temporary to a function except by taking the argument by non-const
(lvalue) reference, which is too restrictive to help in this case.
The following code is perfectly safe in C++11:
std::vector<int> get_vec();
// as lvalue
std::vector<int> const v = get_vec();
write_all_t
On October 15, 2014 7:38:37 PM EDT, Indiana
I decided to take the opportunity to examine a possibility that a couple people have asked about: whether or not these
C++11 facilities can be made available to C++98 code. Looking into that
possibility is what's kept me occupied recently.
That's where I hit a little snag.
It turns out is probably *possible* to implement the facilities in C++98... however, it is impossible to do so in a way that guarantees safety. The problem is that C++98 has no way to prevent you from passing a temporary to a function except by taking the argument by non-const (lvalue) reference, which is too restrictive to help in this case.
[snip rationale]
So unless anyone objects, I'm going to go ahead and make this "RangeIO"
library require C++11. (Specifically, I'm going to make it require rvalue references - I'm not going to use any more C++11 functionality than is absolutely necessary, so no auto or non-static member initializers, etc.. For example, I will have it use std::initializer_list, but only if BOOST_NO_CXX11_HDR_INITIALIZER_LIST is not defined.)
I see no problem with that. ___ Rob (Sent from my portable computation engine)
-----Original Message----- From: Boost [mailto:boost-bounces@lists.boost.org] On Behalf Of Indiana Sent: 16 October 2014 00:39 To: boost@lists.boost.org Subject: Re: [boost] [Range] New I/O functions for ranges
<snip>
However, I decided to take the opportunity to examine a possibility that a couple people have asked about: whether or not these C++11 facilities can be made available to C++98 code. Looking into that possibility is what's kept me occupied recently.
So my options come down to this: 1) Declare the library C++11-or-better-only. That can be enforced by ensuring that BOOST_NO_CXX11_RVALUE_REFERENCES is not defined, and issuing an error message otherwise.
OK that's Fine.
So unless anyone objects, I'm going to go ahead and make this "RangeIO" library require C++11. (Specifically, I'm going to make it require rvalue references - I'm not going to use any more C++11 functionality than is absolutely necessary, so no auto or non-static member initializers, etc.. For example, I will have it use std::initializer_list, but only if BOOST_NO_CXX11_HDR_INITIALIZER_LIST is not defined.)
OK that's considerate. Go for it. Paul --- Paul A. Bristow Prizet Farmhouse Kendal UK LA8 8AB +44 (0) 1539 561830
On Wed, Oct 15, 2014 at 7:38 PM, Indiana
The only way to prevent binding to temporaries in C++98 is to take arguments by non-const (lvalue) reference, but if write_all() does that then the "as lvalue" code above won't work.
So my options come down to this: 1) Declare the library C++11-or-better-only. That can be enforced by ensuring that BOOST_NO_CXX11_RVALUE_REFERENCES is not defined, and issuing an error message otherwise. 2) Allow the library to be used in C++98 code, with a warning - either in documentation or, less than ideally, as a diagnostic - about temporaries. Note that there is no way to detect or diagnose if the warning is ignored, deliberately or accidentally, so things that are perfectly legal in C++11 mode can silently introduce undefined behaviour in C++98 mode.
A third potential option would be to restrict the API based on BOOST_NO_CXX11_RVALUE_REFERENCES. That is, when that macro is set, make write_all() accept non-const reference. This could potentially allow *some* use of the library even with C++03 code -- say, with a local vector -- without loss of safety.
I don't think it's a worthwhile to introduce potentially undetectable bugs. Better to say "if you can't use it safely, you can't use it"
I agree with the above. I simply suggest that even in C++03, you could potentially support a safe subset of desirable use cases.
On 14-11-24 11:03 AM, Nat Goodspeed wrote:
A third potential option would be to restrict the API based on BOOST_NO_CXX11_RVALUE_REFERENCES. That is, when that macro is set, make write_all() accept non-const reference. This could potentially allow *some* use of the library even with C++03 code -- say, with a local vector -- without loss of safety. I've actually been working on a different strategy - an entirely separate interface for C++98 support.
I agree with the above. I simply suggest that even in C++03, you could potentially support a safe subset of desirable use cases. I agree. The functionality offered by write_all (et al) is not just syntactic sugar hiding a for loop - it also handles formatting and makes error checking easy (and, in some cases, practical when it wasn't otherwise). I could probably have submitted a first draft of this
The problem with the C++11 interface is that while you get a really nice interface, the actual write operation may be deferred for quite some time. That's where the problem comes up in C++98: without rvalue references to capture temporaries, your temporary iterable object may have died in the meantime. So instead of the C++11 interface that allows inlining: auto p = write_all(v, ", "); cout << "{ " << p << " }"; // now query p for information about the write The interface I'm putting together for C++98 support is a separate function: cout << "{ "; write_all_result_t r = write_all_compat(cout, v, ", "); cout << " }"; // now query r for information about the write The C++98 compatibility interface is clunkier, but on the other hand, since the iterable is used immediately you don't have to worry about it expiring prematurely. In C++98 - or rather, when BOOST_NO_CXX11_RVALUE_REFERENCES is not defined, the C++11 interface is simply not available. What I've been mucking around with is the question of whether "write_all_compat" should be *just* a compatibility interface for C++98, or an actual first-class alternative interface. It's clunkier than the original interface, but on the other hand it's got kind of a functional flavour. The motivating case I've been musing over is doing background writes: // With the C++11 interface: auto f = async([&o, &v] { return o << write_all(v, ", "); }); // With the C++98 interface (in C++11, of course): auto f = async(write_all_compat, ref(o), cref(v), ", "); Personally, I find the second not only shorter (and it might be even shorter if the function is given a better name, like "write_all_to" or "do_write_all"), but clearer. library a couple weeks ago, if I'd ignored C++98 support, but I consider it important enough to spend some time working on. Whatever form it takes, this library will *definitely* offer C++98 support. Indi
participants (5)
-
Edward Diener
-
Indiana
-
Nat Goodspeed
-
Paul A. Bristow
-
Rob Stewart