Why does reading a record struct fields from std::istream fail, and how can I fix it?

后端 未结 9 2373
野性不改
野性不改 2020-11-21 11:40

Suppose we have the following situation:

  • A record struct is declared as follows

struct Person {
    unsigned int id;
    std::st         


        
9条回答
  •  庸人自扰
    2020-11-21 12:15

    Another solution is to require certain delimiter characters for a particular field, and provide a special extraction manipulator for this purpose.

    Let's suppose we define the delimiter character ", and the input should look like this:

    1267867 "John Smith"      32   
    67545   "Jane Doe"        36  
    8677453 "Gwyneth Miller"  56  
    75543   "J. Ross Unusual" 23  
    

    Generally needed includes:

    #include 
    #include 
    #include 
    

    The record declaration:

    struct Person {
        unsigned int id;
        std::string name;
        uint8_t age;
        // ...
    };
    

    Declaration/definition of a proxy class (struct) that supports being used with the std::istream& operator>>(std::istream&, const delim_field_extractor_proxy&) global operator overload:

    struct delim_field_extractor_proxy { 
        delim_field_extractor_proxy
           ( std::string& field_ref
           , char delim = '"'
           ) 
        : field_ref_(field_ref), delim_(delim) {}
    
        friend 
        std::istream& operator>>
           ( std::istream& is
           , const delim_field_extractor_proxy& extractor_proxy);
    
        void extract_value(std::istream& is) const {
            field_ref_.clear();
            char input;
            bool addChars = false;
            while(is) {
                is.get(input);
                if(is.eof()) {
                    break;
                }
                if(input == delim_) {
                    addChars = !addChars;
                    if(!addChars) {
                        break;
                    }
                    else {
                        continue;
                    }
                }
                if(addChars) {
                    field_ref_ += input;
                }
            }
            // consume whitespaces
            while(std::isspace(is.peek())) {
                is.get();
            }
        }
        std::string& field_ref_;
        char delim_;
    };
    

    std::istream& operator>>
        ( std::istream& is
        , const delim_field_extractor_proxy& extractor_proxy) {
        extractor_proxy.extract_value(is);
        return is;
    }
    

    Plumbing everything connected together and instantiating the delim_field_extractor_proxy:

    int main() {
        std::istream& ifs = std::cin; // Open file alternatively
        std::vector persons;
    
        Person actRecord;
        int act_age;
        while(ifs >> actRecord.id 
                  >> delim_field_extractor_proxy(actRecord.name,'"')
                  >> act_age) {
            actRecord.age = uint8_t(act_age);
            persons.push_back(actRecord);
        }
    
        for(auto it = persons.begin();
            it != persons.end();
            ++it) {
            std::cout << it->id << ", " 
                          << it->name << ", " 
                          << int(it->age) << std::endl;
        }
        return 0;
    }
    

    See the working example here.

    NOTE:
    This solution also works well specifying a TAB character (\t) as delimiter, which is useful parsing standard .csv formats.

提交回复
热议问题