Home > Enterprise >  std::bit_cast vs reinterpret_cast in file I/O
std::bit_cast vs reinterpret_cast in file I/O

Time:06-12

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)?

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.

  • Related