Search code examples
c++boostboost-spiritboost-regexboost-spirit-qi

C++/Boost: Writing a more powerful sscanf replacement


I want to write a function in C++ to replace C's sscanf that assigns the matches to iterator.

Basically, I want something like:

string s = "0.5 6 hello";
std::vector<boost::any> any_vector;
sscanv(s, "%f %i %s", any_vector);
cout << "float: " << any_cast<float>(any_vector[0]);
cout << "integer: " << any_cast<integer(any_vector[1]);
cout << "string: " << any_cast<string>(any_vector[2]);

The exact details may vary, but you get the idea. Any ideas for implementation?

Options so far along with problems so far:

  • std::istringstream: there's no manipulator for matching constant expressions
  • Boost.Regex: not sure if this will work and it seems much more complicated than necessary for this
  • Boost.Spirit: don't think this will work for dynamically generated format strings and it also seems more complicated then necessary
  • sscanf: it would work, but is non-standard, etc, and using it would require a lot of overhead since the number of arguments is determined at compile time

Solution

  • What's about that?

    void sscanf(std::string str,
                const std::string& format,
                std::vector<boost::any>& result)
    {
      std::string::const_iterator i = format.begin();
      while (i != format.end())
      {
        if (*i == '%')
        {
          ++i; // now *i is the conversion specifier
          char specifier = *i;
    
          ++i; // now *i is the next seperator
          std::string extract = str.substr(0, str.find(*i));
    
          switch (specifier) 
          {
            // matching an integer
            case 'i':
              result.push_back(boost::lexical_cast<int>(extract));
              break;
            // matching a floating point number
            case 'a': case 'e': case 'f': case 'g':
              result.push_back(boost::lexical_cast<float>(extract));
              break;
            // matching a single character
            case 'c':
              result.push_back(boost::lexical_cast<char>(extract));
              break;
            // matching a string
            case 's':
              result.push_back(extract);
              break;
            // Invalid conversion specifier, throwing an exception
            default:
              throw std::runtime_error("invalid conversion specifier");
              break;
          }
        }
        else
        {
          // if it's not a %, eat!
          str.erase(0, str.find(*i)+1);
          ++i;
        }
      }
    }
    

    Some conversions specifiers are missing – but principally it works.