Problem
I was just trying to debug a set of file-manipulation routines I wrote for a program I am working on. One of them kept returning an INVALID_HANDLE
error.
Explanation
I figured out what the problem was. It turns out that because I provided several overloads so that I could call the function with either a file handle or filename, I ended up creating ambiguous overloads.
For example (not variadic functions; the ellipses are just for simplification purposes):
INT CopyFileSection ( HANDLE fIn, HANDLE fOut, … );
INT CopyFileSection ( HANDLE fIn, TCHAR* fOut, … );
INT CopyFileSection ( TCHAR* fIn, HANDLE fOut, … );
INT CopyFileSection ( TCHAR* fIn, TCHAR* fOut, … );
The first one (HANDLE, HANDLE
) does the main work while the others just open the file pointed to by the filename and call the first function.
The problem is that a HANDLE
is just a pointer, so the compile can’t figure out which one I am calling (even though to me it was obvious), and ends up calling HANDLE, HANDLE
even when I pass a pointer to a string, so naturally it fails since the pointer is not a file handle.
Question
I want to provide maximum flexibility, so short of re-writing to use std::string
instead of TCHAR*
for the filenames (which I actually did include as well), what suggestions are there to deal with this sort of scenario?
Related
As a related side note—a separate question?—I was wondering about the ease, safety, and feasibility of providing overloads for all (or at least a set of) possible permutations.
For example, with a function that takes two files, you could use HANDLE
, TCHAR*
, and string
(probably others, but in this case we’ll stick with these three). This means there are up to nine overloads just for the files: HH, HT, HS, TH, TT, TS, SH, ST, SS
. Let alone if there are other arguments that could provide more overloads. Surely there must be a better way to both provide flexibility in calling the function and clean, understandable, and maintainable code.
The easiest way to limit the combinatorial explosion of your overloads is to introduce a small helper class that can accept all the various types and convert them to a single common type.
For example
class FileHelper {
public:
FileHelper(HANDLE& h) : handle(h) {}
FileHelper(const TCHAR* fName) : handle(openFile(fName) {}
FileHelper(const std::string& fName) : handle(openFile(fName) {}
HANDLE getHandle() const { return handle; }
private:
HANDLE handle;
}
INT CopyFileSection ( FileHelper fIn, FileHelper fOut, … );
There is only one overload of CopyFileSection
and that overload takes two FileHelper
instances as stand-ins for a pair of file handles. The FileHelper
class takes care of accepting the different possibilities (existing handle, file name, etc.) and obtaining a file handle for each of them.
Note that the first FileHelper
constructor takes a non-const HANDLE
reference. This was done deliberately to inhibit conversions from types that could be converted implicitly to a HANDLE
, such as (other) pointer types. This has the side-effect that you also can’t pass directly handles that were returned by a function, but they need to be stored in a variable first.
2