views:

340

answers:

6
std::stringstream convertor("Tom Scott 25");
std::string name;   
int age;

convertor >> name >> age;

if(convertor.fail())
{
    // it fails of course
}

I'd like to extract two or more words to one string variable. So far I've read, it seems that it is not possible. If so, how else to do it? I'd like name to get all characters before number (the age).

I'd feel most comfortable using sscanf, but I obviously can't.

What I need is ability to extract all words before age for example.

+2  A: 

What’s wrong with this?

std::stringstream convertor("Tom Scott 25");
std::string firstname;   
std::string surname;
int age;

convertor >> firstname >> surname >> age;
std::string name = firstname + " " + surname;
Konrad Rudolph
Because it is only an example. I'll face cases when I need to extract unknown number of words.
Balon
Too obvious, perhaps?
anon
@Balon Then why didn't you ask about that, rather than explicitly stating you wanted to read TWO words?
anon
`I'd like to extract two words or more to one string variable.` Anyway, my mistake :) I could write my question better.
Balon
@Neil, he did specifically say he needed to read everything up to the age, not necessarily just two words.
Jerry Coffin
Not in the question description - I've now edited it.
anon
Thanks, Neil, and sorry ;)
Balon
A: 

One approach would be to make a new class with an overloaded operator>>

class TwoWordString {
public:
    std::string str;
};

ostream& operator>>(ostream& os; TwoWordString& tws) {
    std::string s1, s2;
    os >> s1;
    os >> s2;
    tws.str = s1 + s2;
    return os;
}
Christopher Bruns
This has the same problem -- it still only reads two words. As an aside, it also has a minor bug/typo (you extract from an istream, not an ostream).
Jerry Coffin
+1  A: 

What's wrong with this?

std::stringstream convertor("Tom Scott 25");


std::string first, last;
int age;

convertor >> first >> last >> age

If you really want to read first and last in one go, something like this will work

class Name {
  std::string first, last;

 public:

  std::istream& read(std::istream& in) {
    return in >> first >> last;
  }

  operator std::string() const { return first + " " + last; }
};

std::istream& operator>>(std::istream& in, Name& name) {
  return name.read(in);
} 

/* ... */

Name name;
int age;

converter >> name >> age;
std::cout << (std::string)name; 

A more generic example where you wanted to read N words could function like this:

class Reader {
int numWords;
std::vector<std::string> words;
// ... 
std::istream& read(std::istream& in) {
  std::vector<std::string> tmp;
  std::string word;
  for (int i = 0; i < numWords; ++i) {
    if (!in >> word)
      return in;
    tmp.push_back(word);
  }

  // don't overwrite current words until success
  words = tmp;
  return in;
}
meagar
I like your first sentence. :-)
Konrad Rudolph
+2  A: 

Most of the solutions posted so far don't really meet the specification -- that all the data up to the age be treated as the name. For example, they would fail with a name like "Richard Van De Rothstyne".

As the OP noted, with scanf you could do something like: scanf("%[^0-9] %d", name, &age);, and it would read this just fine. Assuming this is line oriented input, I'd tend to do that anyway:

std::string temp;
std::getline(infile, temp);

// technically "[^0-9]" isn't required to work right...
sscanf(temp.c_str(), "%[^0123456789] %d", name, &age);

Unfortunately, iostreams don't provide a direct analog to a scanset conversion like that -- getline can read up to a delimiter, but you can only specify one character as the delimiter. If you really can't use scanf and company, the next stop would be either code it by hand (the beginning of the age would be temp.find_first_of("0123456789");) or use an RE package (TR1 if your compiler supplies it, otherwise probably Boost).

Jerry Coffin
I've said that I can't use sscanf, because it doesn't support `std::string`, so far I've read anyway.So in `sscanf(temp.c_str(), "%[^0123456789] %d", name, ` the `name` variable wouldn't can be a `std::string`.
Balon
@Balon:that's correct -- you'd have to allocate a temporary buffer, read into that, and then create a string from the buffer. Alternatively, you could use a string's buffer with scanf, but it's a bit trickier.
Jerry Coffin
A: 

General algorithm that you could implement:

read word into name
loop
   try reading integer
   if success then break loop
   else
      clear error flag
      read word and attach to name 
David Rodríguez - dribeas
A: 

Here's the overkill way (using Boost.Spirit) >:D

#include <iostream>
#include <string>
#include <boost/format.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_fusion.hpp>

int main()
{
    namespace qi = boost::spirit::qi;
    namespace phoenix = boost::phoenix;
    namespace ascii = boost::spirit::ascii;
    using ascii::char_; using ascii::digit; using ascii::blank;
    using qi::_1; using qi::int_; using phoenix::ref; using phoenix::at_c;

    std::string input("Sir  Buzz Killington, esq. 25");
    std::string name;
    int age = 0;

    qi::rule<std::string::const_iterator, std::string()> nameRule;
    nameRule %= (+(char_ - digit - blank));

    std::string::const_iterator begin = input.begin();
    std::string::const_iterator end = input.end();
    qi::parse(begin, end,
        (
                nameRule[ref(name) += _1]
            >> *( ((+blank) >> nameRule)[ref(name) += ' ']
                                        [ref(name) += at_c<1>(_1)] )
            >> *blank
            >>  int_[ref(age) = _1]
        )
    );

    std::cout << boost::format("Name: %1%\nAge: %2%\n") % name % age;
    return 0;
}

Output:

Name: Sir Buzz Killington, esq.

Age: 25

Seriously though, if you often do non-trivial input parsing in your program, consider using a parsing or regular expressions library.

Emile Cormier