std::basic_istream<...>::eof()
only returns true if a recent extraction set the appropriate bit in the stream state. When a file stream is constructed, its stream state is always that of std::ios_base::goodbit
.
An alternative is to check if the next available character is the EOF character:
if (f.peek() == std::char_traits<char>::eof())