The getline case (boolean and possible value) really wants a type like Haskell's "Maybe" or Rust's Option: a type that either contains nothing or a value.
Unfortunately the currently available solution (boost::optional) performs enough worse that the difference can be measured in a simple implementation of "wc -l". Consider the following code:
#include <iostream>
#include <string>
#include <boost/optional.hpp>
using namespace std;
using namespace boost;
optional<string> getline_(istream &st) {
std::string line;
if(getline(st, line)) return line;
return none;
}
int main() {
int lines = 0;
while(getline_(cin)) lines++;
cout << lines << "\n";
}
vs
using namespace std;
int main() {
int lines = 0;
string line;
while(getline(cin, line)) lines++;
cout << lines << "\n";
}
Run on an input file of 172,544 lines (the GPL-3 license repeated 256 times), the version using "optional" takes 400ms and performs 1,077,504(!) heap allocations, while the more standard version takes just 280ms and performs just 8 allocations.
Of course, "wc -l" (GNU) takes just 9ms, so clearly this isn't a great program either way--but it does serve to show that boost::optional can be a surprising performance sink compared to using a bool return value + out parameter.
That's not the fault of optional though. It's because with the optional version you're creating (and destroying) a new string each iteration of the loop.
Something like this (untested) should be much closer to the non-optional version:
#include <iostream>
#include <string>
#include <boost/optional.hpp>
using namespace std;
using namespace boost;
optional<string&> getline_(istream &st, string& line) {
if(getline(st, line)) return line;
return none;
}
int main() {
int lines = 0;
string line;
line.reserve( 1024 ); //1024 should be long enough for any line
while(getline_(cin, line )) lines++;
cout << lines << "\n";
}
Yes for this case, but that's just because it's a bad example for comparing the performance of optional, because the code is doing things that have very different performance characteristics.
There are plenty of other cases though where it is useful to use optional (e.g. instead of passing/returning a naked pointer which may or may not be null) and in those cases use of optional will have little/no overhead.
The basic getline interface removes heap pressure by reusing the same string over and over, even though semantically the optional<string> approach may look cleaner. Eric Niebler showed a while ago (I can't remember where) that this API lends itself perfectly to ranges, and can provide both a reasonable interface without sacrificing performance. IIRC, the API looked somewhat like this:
for (auto& line : getline_range(cin))
f(line); // do something with line, e.g., parse.
The semantics are: iterate over standard input until EOF or error and let the range keep (and reuse) the string internally while exposing it as const-reference by dereferencing.
In this case, passing the std::string to std::getline by reference is more than just an "out parameter": it is the buffer in which the result should be stored. Of course this will be more efficient than allocating a new buffer for each line -- and that is a good reason why this particular interface would be worse off with multiple return values.
The std::unordered_map::insert example in the article is an excellent example of an interface with multiple return values: it returns a pair (iterator, bool) of an iterator to the given key and a bool indicating if the iterator is to a newly inserted key-value pair or if it was the key-value pair already stored in the map. Neither the iterator nor the bool owns a buffer like in the std::getline example, so the std::getline out parameter design is not needed here.