Search code examples
c++fstreamstdarray

How to read bytes from file using std::ifstream to std::array?


The below program tries to open a rom file and loads it to std::array.

#include <array>
#include <fstream>
#include <iostream>

const std::string ROM_FILE = "cpu_instrs.gb";

int main()
{
    std::array<uint8_t, 0x8000> m_Cartridge;
    std::ifstream istream(ROM_FILE, std::ios::in | std::ios::binary);
    
    istream.seekg(0, std::ios::end);
    size_t length = istream.tellg();
    istream.seekg(0, std::ios::beg);
    
    if (length > m_Cartridge.size())
    {
        length = m_Cartridge.size();
    }
    
    istream.read(m_Cartridge.data(), length);
    
    for (const uint8_t& b : m_Cartridge)
    {
        std::cout << b << std::endl;
    }
    
    return 0;
}

When I ran the program above using g++, I got the following error

test.cpp: In function 'int main()':
test.cpp:21:34: error: invalid conversion from 'std::array<unsigned char, 32768>::pointer' {aka 'unsigned char*'} to 'std::basic_istream<char>::char_type*' {aka 'char*'} [-fpermissive]
   21 |     istream.read(m_Cartridge.data(), length);
      |                  ~~~~~~~~~~~~~~~~^~
      |                                  |
      |                                  std::array<unsigned char, 32768>::pointer {aka unsigned char*}
In file included from C:/TDM-GCC-64/lib/gcc/x86_64-w64-mingw32/9.2.0/include/c++/fstream:38,
                 from test.cpp:2:
C:/TDM-GCC-64/lib/gcc/x86_64-w64-mingw32/9.2.0/include/c++/istream:486:23: note:   initializing argument 1 of 'std::basic_istream<_CharT, _Traits>& std::basic_istream<_CharT, _Traits>::read(std::basic_istream<_CharT, _Traits>::char_type*, std::streamsize) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_istream<_CharT, _Traits>::char_type = char; std::streamsize = long long int]'
  486 |       read(char_type* __s, streamsize __n);
      |            ~~~~~~~~~~~^~~

It seems to me that std::istream only works with char not unsigned char. When I change type from uint8_t to char. The program compiles and ran without problems. Is there any way to make std::istream work with uint8_t?


Solution

  • std::istream is written in terms of char_type being simply char, and similarly, std::istream::read is the same.

    The conventional approach would be to reinterpret_cast<char*> the pointer that you are reading to:

    istream.read(reinterpret_cast<char*>(m_Cartridge.data()), length);
    

    Although using reinterpret_cast is often frowned upon in many cases, this is one such case where it's both necessary and also safe to perform, since pointers to char are capable of aliasing any object with the same reachability as the parent object (in this case, the same reachability as the entire array object).

    In fact, if you check the std::istream::read page on cppreference, even it uses a similar example to read the raw binary:

    std::uint32_t n;
    if(raw.read(reinterpret_cast<char*>(&n), sizeof n)) { ... }
    

    That said, there is technically an alternative way you can do this -- though it's not recommended. The various stream classes are templated on the character type (basic_*stream<...>), of which std::ifstream is an alias of. Technically you could try to instantiate std::basic_ifstream<unsigned char> and read from this without a cast. However, in doing so, you may need to implement a custom std::char_traits -- as this is not required by the C++ standard to work with signed or unsigned character types (e.g. std::char_traits<unsigned char> is not guaranteed to be valid)