Lucretiel
Lucretiel

Reputation: 3324

Parse a whole (beginning to end) string in C++, including all whitespace

I'm looking for a way to generically parse a whole string in C++. What I mean by that is that the entire string is used in the parse, or else it's an error. This means that the naive operator >> behavior, which delimits on whitespace for most types, is not what I want (though I'd be perfectly happy if the solution involved it). Some examples:

// Parse a whole value, throwing an exception if there's a parse error
void parse_into<typename T>(const char* input, T& result);

double value = 0;
parse_into("12.5", value);  // #1: Success
parse_into("ABC", value);  // #2: Fail
parse_into("12.5 abc", value);  //#3: Fail

std::string str_value;
parse_into("ABC", str_value);  // #4: Success
parse_into("ABC ABC", str_value);  // #5: Success; str_value == "ABC ABC"
parse_into("ABC  ABC", str_value);  // #6: Success; str_value == "ABC  ABC'

I'm confident that what I want revolves around stringstream and operator >>. I can see how to detect if not the whole string was parsed (case #3), by checking if the stringstream is empty. However, what I can't figure out is a generic way to allow for types that can be constructed from any string, like std::string. Perhaps a SIFNAE overload involving types that can be constructed from a const char*?

I don't have a particular formal requirement for the final interface; what I want is for it to do what feels like the "right thing" for any given type. For context, this is for a command-line argument parsing library. The library handles detecting word boundaries, so I know that the input to parse_into is the entire word I'm trying to read.

Upvotes: 1

Views: 81

Answers (1)

bitmask
bitmask

Reputation: 34618

For a double this can be achieved with std::stod after first reading your input as std::string.

std::optional<double> parse(std::string const& str) {
  std::size_t num = 0;
  try {
    auto const res = std::stod(str,&num);
    if (num < str.size()) return std::nullopt;
    return res;
  } catch (...) {
    return std::nullopt;
  }
}

You can create overloads (using a passed in out value, for example) for different types. I'm using an std::optional here, but you can also throw or use any other means of communicating an error.

For instance:

bool parse(std::string const& str, double& out);
bool parse(std::string const& str, std::uint32_t& out);
bool parse(std::string const& str, std::string& out); // always succeeds
// ...

Edit: A more general interface that allows to pass any type of stoXYZ function:

  template <typename T>
  static void parse(std::string const& s, T (*fun)(std::string const&,std::size_t*,int), std::optional<T>& out) { /**/ }   
  template <typename T>
  static void parse(std::string const& s, T (*fun)(std::string const&,std::size_t*), std::optional<T>& out) { /**/ }

This takes a pointer-to-function, but it could also take a std::function object. The two overloads exist because std::stoi and std::stod have different argument lists (the integer ones want a base).

Upvotes: 2

Related Questions