2

I'm reading an ascii file this way:name1|name2|name3|name4|name5|name6|name7||||||||||name8|||name9 It consists in a bunch of names separated by the '|' char, some of the slots are empty. I'm using the following code to read the list of names:

#include <iostream>
#include <fstream>
#include <vector>
#include <string>

int main() {
    std::ifstream File;
    File.open("File.txt");
    if (!File.is_open())
        return -1;

    std::vector<std::string> Names;
    char Buffer[0xff];
    while (!File.getline(Buffer,16,'|').eof()) {
        if (strlen(Buffer) == 0)
            break;
        Names.push_back(Buffer);

        std::cout << strerror(File.rdstate()) << std::endl;
    }

    return 0;
}

It is working as it should, it reads a name each time, but for some reason if it hits the char count on the second argument of File.getline() and does not find the delimiting char, it closes itself. This is the console output I've got after running this code on a file with a big name:

File: File.txt
Ana|Bjarne Stroustrup|Alex

Console:
No error
No such file or directory

It reads the first name on the list successfully, but when it tries to read the second name, it doesn't hit the delimiting char, and for some reason it leads to the file closing by itself. I hope someone can explain to me why does this happen.

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
Azazel
  • 23
  • 4
  • 1
    You should check the state of the stream between the `getline` and the `strlen` function calls. You may want to read this: [Why is iostream::eof inside a loop condition (i.e. `while (!stream.eof())`) considered wrong?](https://stackoverflow.com/q/5605125/12149471) – Andreas Wenzel Oct 31 '21 at 15:05
  • Questions seeking debugging help should provide a [mre] with exact input, expected output and actual output. – Andreas Wenzel Oct 31 '21 at 15:12
  • [The documentation](https://en.cppreference.com/w/cpp/io/basic_istream/getline) says that, if `getline` reads `count-1` characters and doesn't encounter the delimiter, `failbit` is set. Once the stream is in this failed state, all subsequent operations fail until this bit is cleared. – Igor Tandetnik Oct 31 '21 at 15:13
  • @AndreasWenzel Ok, I've edited it, is the code better this way? – Azazel Oct 31 '21 at 15:13
  • @AndreasWenzel I've provided all the input, you just need to put this code on your compiler and write a file named "File.txt" with "Ana|Bjarne Stroustrup|Alex" inside of it, then run your code in this file. – Azazel Oct 31 '21 at 15:15
  • @IgorTandetnik So how can I clear this flag? – Azazel Oct 31 '21 at 15:15
  • 1
    [File.clear()](https://en.cppreference.com/w/cpp/io/basic_ios/clear). The problem though is that the remainder of the long name is still in the stream, together with the delimiter following it, so now you need to figure out how to deal with that. Why don't you use [the version of `getline`](https://en.cppreference.com/w/cpp/string/basic_string/getline) that takes `std::string`? It doesn't have any length limitations. Especially since you are storing the name as `std::string` in the end anyway. – Igor Tandetnik Oct 31 '21 at 15:17
  • 1
    @Azazel: Yes, the new version of the code is better. However, I would change the line `while (!File.getline(Buffer,16,'|').eof()) {` to `while ( File.getline(Buffer,16,'|') ) {`. That has two advantages: It is simpler and it also stops the loop on an error condition, not just an end-of-file condition. – Andreas Wenzel Oct 31 '21 at 15:17
  • @AndreasWenzel Except that stopping the loop on any error condition seems to be precisely what the OP *doesn't* want to do. They want to somehow deal with `failbit` condition triggered by the field not fitting into the buffer. – Igor Tandetnik Oct 31 '21 at 15:20
  • @IgorTandetnik Thank you!!! It really solved the issue. I didn't knew these flags could affect the way std::ifstream would work. I didn't use std::getline because I'm on a slightly different and more complex situation where I need to set a limit of characters to be read. The only weird thing is that I've got this error now: "Operation not permitted". But the program is working as it should now, so I think it doesn't matter so much. If you want, you can write this as an answer to gain more visibility and help others. – Azazel Oct 31 '21 at 15:23
  • @Azazel: A [mre] should generally include a function `main` and all necessary `#include` directives, so that it is only necessary to copy & paste the code in order to reproduce the problem. Also, before posting such a [mre], you should verify that is actually reproduces the problem. If the code you are posting is not a [mre], but is part of a larger program, then it is possible that the problem is not in the posted code, but rather somewhere else in your program. I have experienced this many times that people posted code excerpts, but the actual problem was somewhere else in the program. – Andreas Wenzel Oct 31 '21 at 15:26
  • @AndreasWenzel Yes, I apologize about this, I forgot about the includes and the main function, this is the full code as it was before I've fixed it in case you wanna check it out: https://codeshare.io/6pxRLz And this is the text file: https://codeshare.io/wnPjYB – Azazel Oct 31 '21 at 15:35
  • @Azazel: Even if you consider the problem solved, you may want to [edit] your question to provide such a [mre]. That will increase the chance of your question being upvoted and decrease the chance of it being downvoted and closed. See [this link](https://idownvotedbecau.se/nomcve/) for information on why some people consider a missing [mre] worth a downvote. Also, you may want to consider removing the `#include ` header and removing the call to `OutputDebugStringA` (or replacing it with a call to `std::cout`), so that non-Windows users can also reproduce your problem. – Andreas Wenzel Oct 31 '21 at 15:43
  • @Azazel: Yes, the question looks good now. I have upvoted it. – Andreas Wenzel Oct 31 '21 at 16:18

2 Answers2

2

The expression strerror(File.rdstate()) is not meaningful. The expression File.rdstate() returns an integer which represents the bits of the state flags of the stream. This is not an error code. However, the function strerror expects an error code.

Therefore, calling strerror(errno) or perror(nullptr) may be more meaningful, in general. But in this case, both just return or print "No Error".

In your question, you wrote:

It reads the first name on the list successfully, but when it tries to read the second name, it doesn't hit the delimiting char, and for some reason it leads to the file closing by itself. I hope someone can explain to me why does this happen.

The file does not close. The error message

No such file or directory

that you are receiving is misleading. It is a result of your incorrect usage of the function strerror, as described above.

What happens is that if getline is unable to read the deliminating character, it causes the failbit to be set. Due to the stream being in a failed state, the next function call of getline in the next loop iteration immediately fails without attempting to extract any characters. This causes the if statement

if (strlen(Buffer) == 0)

to be true, causing the loop to be aborted.

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
  • Precisely, thanks for the answer. I thought that these internal flags were just used to point to the programmer which state the object is, but not that they could affect directly how the object works. As noted by @IgorTandetnik on the comments, in order to fix my std::ifstream object, I had to use std::ios:clear() on it, so it could work even though it didn't hit a delimiting character. – Azazel Oct 31 '21 at 23:52
1

You could use the below shown program to add names from your File.txt to the std::vector:

#include <iostream>
#include <vector>
#include <string>
#include <fstream>
int main()
{   
    std::ifstream inputFile("File.txt");
    
    std::string wordName;
    
    std::vector<std::string> Names;
    if(inputFile)
    {
        while(getline(inputFile, wordName, '|'))
        {
            if((wordName.size()!=0))
            {
                Names.push_back(wordName);
            }
        }
    }
    else 
    {
        std::cout<<"File could not be opened"<<std::endl;
    }
    
    inputFile.close();
    
    //lets check/confirm if our vector contains all the names correct names 
    for(const std::string &name: Names)
    {
        std::cout<<name<<std::endl;
    }
    return 0;
}

The output of the above program can be seen here.

Jason
  • 36,170
  • 5
  • 26
  • 60
  • Thanks for the answer. This is a good solution, but I still need to use std::istream::getline() function instead of std::getline() because I need the streamsize to be 16 as shown in the sample code. – Azazel Oct 31 '21 at 23:46
  • @Azazel: Why do you need the buffer size function argument to `getline` to be `16`? Why not set it to the actual size of the buffer, which is `255` (`0xff`)? – Andreas Wenzel Nov 01 '21 at 00:54
  • The file I'm trying to read is huge, there's some information on it that I've omitted to not make this question overcomplicated, so I'm using the buffer size as 16 because I don't want to end up hitting another delimiting character from another source of info, if I don't hit any of then, I just append the rest of the info on the last string, for example it will read "|Bjarne Stroustrup;70|" as: "Bjarne Stroustr", then "up;75|". I'll interpret the ';' char as '\0' then append, so the final string is: "Bjarne Stroustr" + "up", "Bjarne Stroustrup". – Azazel Nov 01 '21 at 14:03