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_cast
s with reinterpret_cast
s 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)?
CodePudding user response:
First of, of course all of this only applies if var
and var2
have the same type and this type is trivially-copyable. A very common mistake is to try to write
and read
non-trivially-copyable types like std::string
in this way, which is fundamentally wrong.
std::bit_cast
is not generally safe, because there is no guarantee that const char*
and std::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_cast
is 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 for float
and int
, both of them are the same size and you have an integer value from which you want to retrieve a float
value 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_cast
does 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 a char
array), which is what you want write
to write to the file.
The same applies to the read
case (except that const
can not be used on reads for obvious reasons; const
isn't required for the write
case 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):
int main( )
{
std::uint32_t var { 301 };
{
std::ofstream file { "myfile.bin" };
char buf[sizeof(var)]{};
std::memcpy(buf, &var, sizeof(var));
file.write(buf, sizeof(buf));
}
std::uint32_t var2 { 10 };
{
std::ifstream file { "myfile.bin" };
char buf[sizeof(var2)]{};
file.read(buf, sizeof(buf));
std::memcpy(&var2, buf, sizeof(buf));
}
std::cout << var2 << '\n';
}
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::byte
instead of char
in the interface, but std::byte
has been introduced only with C 17 while iostream
has been around since (before) C 98. You can't convert a std::byte*
to a const*
without another reinterpret_cast
, so it is pointless to go through it.