14

Here's probably a very noobish question for you: How (if at all possible) can I return an ifstream from a function?

Basically, I need to obtain the filename of a database from the user, and if the database with that filename does not exist, then I need to create that file for the user. I know how to do that, but only by asking the user to restart the program after creating the file. I wanted to avoid that inconvenience for the user if possible, but the function below does not compile in gcc:

ifstream getFile() {
    string fileName;
    cout << "Please enter in the name of the file you'd like to open: ";
    cin >> fileName;
    ifstream first(fileName.c_str());
    if(first.fail()) {
        cout << "File " << fileName << " not found.\n";
        first.close();
        ofstream second(fileName.c_str());
        cout << "File created.\n";
        second.close();
        ifstream third(fileName.c_str());
        return third; //compiler error here
    }
    else
        return first;
}

EDIT: sorry, forgot to tell you where and what the compiler error was:

main.cpp:45: note: synthesized method ‘std::basic_ifstream<char, std::char_traits<char> >::basic_ifstream(const std::basic_ifstream<char, std::char_traits<char> >&)’ first required here 

EDIT: I changed the function to return a pointer instead as Remus suggested, and changed the line in main() to "ifstream database = *getFile()"; now I get this error again, but this time in the line in main():

main.cpp:27: note: synthesized method ‘std::basic_ifstream<char, std::char_traits<char> >::basic_ifstream(const std::basic_ifstream<char, std::char_traits<char> >&)’ first required here
wrongusername
  • 18,564
  • 40
  • 130
  • 214

4 Answers4

18

No, not really. ifstream doesn't have a copy constructor, and if you try to return one, that means copying the instance in your function out to wherever the return needs to go.

The usual workaround is to pass in a reference to one, and modify that reference in your function.

Edit: while that will allow your code to work, it won't fix the basic problem. Right now, you're mixing two rather different responsibilities into a single function: 1) obtain a file name, 2) open or create that file. I think if you separate those, the code will be simpler, and make it much easier to eliminate the source of the problem you're seeing.

Edit 2: Using a reference like this works perfectly well without an operator=. The general idea is something like:

int open_file(char const *name, fstream &stream) { 
    stream.open(name);
}

The assignment operator is neither necessary nor useful in this case -- we simply use the existing fstream via the reference. An operator= would be necessary if and only if we had to pass the argument to the ctor. With a stream, we can default construct a stream that doesn't connect to a file, and then use open to connect to the file after the fact.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • Thanks! I did that, but unfortunately the code still does not work. Using "ifstream database = *getFile();" I get an error on that line of "main.cpp:29: note: synthesized method ‘std::basic_ifstream >::basic_ifstream(const std::basic_ifstream >&)’ first required here" – wrongusername Mar 08 '10 at 05:25
  • The "trick" of passing in a reference to avoid returning a new object will only work for types that have an `operator=`. std::ifstream doesn't have that either, so this advice won't work for you. – MSalters Mar 08 '10 at 14:16
8
bool checkFileExistence(const string& filename)
{
    ifstream f(filename.c_str());
    return f.is_open();
}

string getFileName()
{
    string filename;
    cout << "Please enter in the name of the file you'd like to open: ";
    cin >> filename;
    return filename;
}

void getFile(string filename, /*out*/ ifstream& file)
{
    const bool file_exists = checkFileExistence(filename);
    if (!file_exists) {
        cout << "File " << filename << " not found." << endl;
        filename = getFileName();  // poor style to reset input parameter though
        ofstream dummy(filename.c_str();
        if (!dummy.is_open()) {
            cerr << "Could not create file." << endl;
            return;
        }
        cout << "File created." << endl;
    }
    file.open(filename.c_str());
}

int main()
{
    // ...
    ifstream file;
    getFile("filename.ext", file);
    if (file.is_open()) {
        // do any stuff with file
    }
    // ...
}
Corwin
  • 575
  • 2
  • 9
  • Thanks!!! after a bit of tinkering with your code I finally got what I wanted. :D – wrongusername Mar 08 '10 at 20:22
  • 6
    While this code snippet may solve the question, [including an explanation](http://meta.stackexchange.com/questions/114762/explaining-entirely-‌​code-based-answers) really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion. – NathanOliver Oct 18 '16 at 14:54
4

ifstream does not support copy construct semantics (that what the error message basically sais), so you cannot return an ifstream. Return an ifstream* instead, and pass to the caller the responsability to delete the allocate pointer.

Remus Rusanu
  • 288,378
  • 40
  • 442
  • 569
  • Okay... I put in "return &third" and now it warns me that "address of local variable ‘third’ returned." what do I do in main() then? – wrongusername Mar 08 '10 at 05:12
  • 1
    never ever return &stack_variable. Allocate the ifstream with new operator and return the pointer. – Remus Rusanu Mar 08 '10 at 05:29
  • You mean, something like "ifstream returnStream = &first; return returnStream;"? btw, that doesn't work either. it gives me an error: "main.cpp:56: error: conversion from ‘std::ifstream*’ to non-scalar type ‘std::ifstream’ requested main.cpp:57: error: invalid conversion from ‘void*’ to ‘std::ifstream*’" – wrongusername Mar 08 '10 at 05:32
  • 6
    Wrong, I very seriously and strongly suggest you read up on C/C++ memory management before you continue. There are just way too many things you don't know. – Remus Rusanu Mar 08 '10 at 05:37
  • Thanks! sure...I had a long time ago, but must've forgotten most of it :) – wrongusername Mar 08 '10 at 05:46
0

As an option, ifstream may be extended and custom constructor added to new class.

I've extended it to create test resource stream, encapsulating test resource lookup inside of it.

// test_utils.h
class TestResourceStream : public std::ifstream {
    public:
        TestResourceStream(const char* file_path);
};
// test_utils.cpp
namespace fs = std::filesystem;
fs::path test_resource_path(const char* file_path) {
    fs::path path{std::string{"tests/resources/"} + file_path};
    if (!fs::exists(path))
        throw std::runtime_error{std::string{"path "} + 
            fs::absolute(path).c_str() + " does not exist"};
    return path;
}
TestResourceStream::TestResourceStream(const char* file_path)
    :std::ifstream{test_resource_path(file_path).c_str()} {}
// usage in test
TEST_CASE("parse") {
    std::list<GosDump::Expertise> expertises;
    TestResourceStream stream("requests/page_response.json");
    GosDump::Json::parse(expertises, stream);
    REQUIRE(10 == expertises.size());
}