I spotted this tutorial example at compactcpp:
#5 – Parsing strings into “tokens”
#include <iostream> #include <string> //This program is extremely crude, but it indeed works for its intended purpose. //There are many other better ways to do this. int main() { //Here, we use a std::string to hold our input. //We will then turn this into a float once we have read in a "full number". std::string token; char c; //Here's how we store our measurements. float degrees = 0; float minutes = 0; float seconds = 0; std::cout << "Please input a degrees, minutes, seconds measurement as (D,M,S): "; //While we read in characters while(std::cin.get(c)) { //If it is part of a number, it is part of our "token" if (c >= '0' && c <= '9' || c == '.') { token += c; //If we have ended reading a number, let's "cast" to a float //then, reset the token std::string } else if (c == ',' || c == ')') { //All of these if statements translate to: //If X is 0 (since 0 == false in C++) if (!degrees) //We can use the C++11 function called "std::stof", //which will turn read our string until it hits a non-number //character, and then return it as a float. degrees = std::stof(token); else if (!minutes) minutes = std::stof(token); else if (!seconds) seconds = std::stof(token); //We can use a MEMBER function of a std::string object //to clear the std::string for us so that it is empty once again. token.clear(); } //As always, we want to halt if we see a ')', which marks the end //of our input if (c == ')') break; } //This prints out a newline character std::cout << "\n"; //Print out our final variables std::cout << "Degrees: " << degrees << '\n'; std::cout << "Minutes: " << minutes << '\n'; std::cout << "Seconds: " << seconds << '\n'; return 0; }
I thought that was highly unidiomatic C++ code, so I sat down and scribbled down my own version:
#include <sstream>
#include <iostream>
#include <iomanip>
#include <limits>
namespace mylib
{
struct Angle {
double degrees = 0, minutes = 0, seconds = 0;
};
std::ostream& operator<<(std::ostream& os, Angle const& angle) {
std::ostream wrap(os.rdbuf());
wrap.imbue(std::locale("C"));
wrap << std::fixed << std::setprecision(std::numeric_limits<double>::digits10)
<< '('
<< angle.degrees << ", "
<< angle.minutes << ", "
<< angle.seconds
<< ')';
os.setstate(wrap.rdstate());
return os;
}
std::istream& operator>>(std::istream& is, Angle& angle) {
std::istream wrap(is.rdbuf());
wrap.imbue(std::locale("C"));
auto expect = [&](char const expected) {
char actual;
if (!(wrap >> actual))
return false; // state already bad()
if (actual != expected) // force fail
wrap.setstate(wrap.rdstate() | std::ios::failbit);
return actual == expected;
};
expect('(') && (wrap >> angle.degrees)
&& expect(',') && (wrap >> angle.minutes)
&& expect(',') && (wrap >> angle.seconds)
&& expect(')');
is.setstate(wrap.rdstate());
return is;
}
}
int main() {
std::istringstream iss("(1.24, 9001, 0.000000134)");
mylib::Angle parsed;
if (iss >> parsed)
std::cout << "Parsed: " << parsed << "\n";
else
std::cout << "Parsing did not succeed\n";
}
What do you think can be improved here to make it more elegant and robust?
if (iss >> parsed)
always be true since your operator always returnsistream is
? \$\endgroup\$istream&
which has a contextual conversion tobool
(in c++11¹). The conversion will evaluate totrue
iff the stream state isgood()
. [¹ in c++03 it was an implicit conversion tovoid*
] \$\endgroup\$