4

I have been reading "C++ Primer" and in Chapter 8 we discuss I/O. It is stated that I/O objects cannot be copied or assigned. If that is so, then why does the following code compile and run?

std::ifstream get_lines(const std::string &fname, std::vector<std::string> &v)
{
    std::ifstream file(fname);
    if (!file) {
        std::cerr << "Error opening file: " << fname << std::endl;
        return file;
    }

    std::string buf;
    while (std::getline(file, buf))
        v.push_back(buf);

    return file;
}

int main()
{
    std::vector<std::string> v;
    auto f = get_lines("test.txt", v);
    f.close();

    return 0;
}

Wouldn't the return be creating a copy of file which is subsequently used to initialize f?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 5
    That's not a copy. That's a move. – Indiana Kernick Jan 16 '20 at 02:02
  • @Kerndog73 How do I tell when this is going to take place? Also, do you have any resources explaining this behavior? –  Jan 16 '20 at 02:08
  • @dylif Refer to [this post](https://stackoverflow.com/q/12953127/1424875) and [this page](https://en.cppreference.com/w/cpp/language/copy_elision) for more details on copy elision, which allows (and sometimes mandates) that compilers emit a move *even if a copy would be impossible or have visible side-effects*. From a quick glance, your behavior falls under NRVO. – nanofarad Jan 16 '20 at 02:15
  • @ReinstateMonica-ζ-- Thanks, this is starting to make more sense now. –  Jan 16 '20 at 02:26
  • @ReinstateMonica-ζ-- That is an incorrect explanation. NVRO is about omitting the execution of copy/move constructors. They still need to be callable. You haven't explained why the move constructor is chosen instead of the copy constructor. – HTNW Jan 16 '20 at 02:27
  • @HTNW That's why I gave the disclaimer that this is a quick glance, and why I didn;t write a full answer, rather deferring to authorititative/established info. Feel free to do so if you have a chance (or to state the correct information in a comment), or to VTC as a dupe if an existing answer covers this – nanofarad Jan 16 '20 at 02:28
  • @ReinstateMonica-ζ-- If everybody focus more on the ultimate goal of the site - help others instead of focus on principle/rule/license, then all that mess could have not happened. Even for now, it is not late to start to focus on the right thing. – jw_ Jan 16 '20 at 03:15

1 Answers1

3

See this page. When you say

return file;

file is an "id-expression" (i.e. it's the name of some variable). These have special handling under return:

automatic move from local variables and parameters

If expression is a (possibly parenthesized) id-expression that names a variable ... then overload resolution to select the constructor to use for initialization of the returned value ... is performed twice:

  • first as if expression were an rvalue expression (thus it may select the move constructor), ...
  • then overload resolution is performed as usual, with expression considered as an lvalue (so it may select the copy constructor).

You cannot copy an ifstream, that's true, but you can move it. Therefore, the compiler essentially inserts an implicit std::move for you:

return std::move(file);

which allows the move constructor of ifstream to be called, even though the copy constructor is deleted. "Moving" means that any resources "owned" by the istream are transferred to a new object. The move constructor takes ownership of these resources away from the source object (thus modifying it), while giving it to the new object. Contrast the copy constructor, which we generally suppose should not modify the source and so couldn't take its ownership away. This makes a copy constructor unsafe for istream, since it would mean two objects trying to manage one external resource and possibly confusing each other. A move constructor is not bound by the contract to leave the source object untouched, so it can ensure that resources are only owned by one object.

HTNW
  • 27,182
  • 1
  • 32
  • 60
  • 4
    Actually writing `return std::move(file)` would be different though, this suppresses NRVO – M.M Jan 16 '20 at 02:37