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.