Some fun.
namespace file {
template<auto f>
using constant = std::integral_constant< std::decay_t<decltype(f)>, f >;
using file_ptr = std::unique_ptr<std::FILE, constant<std::fclose>>;
FILE* unwrap( file_ptr const& ptr ) { return ptr.get(); }
template<class T> requires (!std::is_same_v< file_ptr, std::decay_t<T> >)
T&& unwrap( T&& t ) { return std::forward<T>(t); }
file_ptr wrap( FILE* ptr ) { return file_ptr(ptr); }
template<class T> requires (!std::is_same_v< file_ptr, std::decay_t<T> >)
T&& wrap( T&& t ) { return std::forward<T>(t); }
template<auto f>
auto call = [](auto&&...args){ return wrap( f( unwrap(decltype(args)(args))... ) ); };
// most FILE* operations can be rebound as follows:
auto open = call<std::fopen>;
auto close = call<std::fclose>;
auto getc = call<std::fgetc>;
auto putc = call<std::fputc>;
// any one that works with raw buffers (like std::fread or std::fwrite) needs more work
}
the core of this is that a unique_ptr<FILE, thing that calls fclose>
is a decent quality FILE*
C++ wrapper.
Then I added some machinery that maps fgetc
and similar functions over to ones that operate on file::file_ptr
by wrapping/unwrapping them.
now you can do:
auto pFile = file::open("hello.txt", "r");
Live example.
auto pFile = file::open("hello.txt", "r");
auto pFileOut = file::open("world.txt", "w");
if (pFile)
{
while(auto c = file::getc(pFile))
{
file::putc(c, pFileOut);
}
}
a more advanced version would take the arguments from a function pointer, toss them in a tuple, map them to-from tuples of munged arguments.
So
char*, int
would map to a std::span<char>
.
The call
helper instead of the naive stuff above would take the munged arguments, map them to tuples of C style arguments, concatinate the tuples together, and std::apply
the C function. Then map the return value.
call
with a bit of work could even have a fixed signature instead of auto&&...
, so IDEs could give hints.