How should one cast pointers to char*/const char*? Using reinterpret_cast? Or probably std::bit_cast?
A simple example:
#include <iostream>
#include <fstream>
#include <bit>
int main( )
{
std::uint32_t var { 301 };
{
std::ofstream file { "myfile.bin" };
file.write( std::bit_cast<const char*>( &var ), sizeof( var ) );
}
std::uint32_t var2 { 10 };
{
std::ifstream file { "myfile.bin" };
file.read( std::bit_cast<char*>( &var2 ), sizeof( var2 ) );
}
std::cout << var2 << '\n';
}
This seems to works fine. Also replacing all the std::bit_casts with reinterpret_casts works fine too.
So which one is more correct in this case? And what about using std::byte* instead of char* for file I/O (though I tried it and it did not compile)?
First of, of course all of this only applies if
varandvar2have the same type and this type is trivially-copyable. A very common mistake is to try towriteandreadnon-trivially-copyable types likestd::stringin this way, which is fundamentally wrong.std::bit_castis not generally safe, because there is no guarantee thatconst char*andstd::uint32_t*use the same representation for the pointer values. They technically don't even need to have the same size. Whether you are going to come into contact with a platform where this is the case, is a different question.The intended purpose of
std::bit_castis to reinterpret the object representation of one value of a given type into another type, giving you a (possibly different) corresponding value of the target type. (For example if you know the representation used forfloatandint, both of them are the same size and you have an integer value from which you want to retrieve afloatvalue corresponding to the same representation.)For pointers this is usually not really what you want. You want the value of the pointer to be preserved (or to get a pointer value to a related object or the object representation). How the representations of the original and converted pointer value relate is secondary.
reinterpret_castdoes the above.reinterpret_cast<const char*>is the standard approach and is almost as correct as you can get. There are some defects in the standard regarding how exactly the result of this cast can be used, but for practical purposes you can just assume that it will always give you a pointer to the first element of the object representation (interpreted as achararray), which is what you wantwriteto write to the file.The same applies to the
readcase (except thatconstcan not be used on reads for obvious reasons;constisn't required for thewritecase either, but shows intent more clearly).If you want a to stay completely in the realm of what the standard currently specifies, I think the following should be fine (except for missing error and end-of-file checking, which could incur undefined behavior):
But practically, any compiler will have the expected behavior with
reinterpret_cast, so there isn't much of a point to this.If you were to design the file IO operations nowadays, you would probably use
std::byteinstead ofcharin the interface, butstd::bytehas been introduced only with C++17 whileiostreamhas been around since (before) C++98. You can't convert astd::byte*to aconst*without anotherreinterpret_cast, so it is pointless to go through it.