#pragma once #include #include #include #include #include class binary_ifstream { public: using char_type = char; using size_type = std::streamsize; [[nodiscard]] inline std::error_code open( const std::filesystem::path& filename, bool check_file_before_open ); [[nodiscard]] inline std::error_code read(std::span buffer); template [[nodiscard]] std::error_code read(T& integer); template [[nodiscard]] std::error_code read_ieee754(T& floating_point); [[nodiscard]] inline std::error_code skip(size_type count); template [[nodiscard]] std::error_code skip(); template [[nodiscard]] std::error_code skip(); inline void close(); protected: [[nodiscard]] inline static std::error_code check_file( const std::filesystem::path& filename ); [[nodiscard]] inline std::errc get_stream_error_code() const; private: std::basic_ifstream in{}; }; inline std::errc binary_ifstream::get_stream_error_code() const { const auto state = in.rdstate(); auto code = std::errc{}; if (state != std::ios::goodbit) { code = std::errc::state_not_recoverable; if (state & std::ios::failbit) // irrecoverable stream error { code = std::errc::state_not_recoverable; } else if (state & std::ios::badbit) // input/output operation failed { code = std::errc::io_error; } else if (state & std::ios::eofbit) // input sequence has reached end-of-file { code = std::errc::result_out_of_range; } } return code; } inline std::error_code binary_ifstream::check_file(const std::filesystem::path& filename) { auto error = std::error_code{}; const auto is_file = std::filesystem::exists(filename, error); if (error) { return error; } if (not is_file) { return std::make_error_code(std::errc::invalid_argument); } const auto files_exists = std::filesystem::exists(filename, error); if (error) { return error; } if (not files_exists) { return std::make_error_code(std::errc::no_such_file_or_directory); } return {}; } inline std::error_code binary_ifstream::open( const std::filesystem::path& filename, bool check_file_before_open ) { if (check_file_before_open) { if (const auto error = check_file(filename)) { return error; } } in = {}; // Turn of exceptions, as errors are handled via the stream state. in.exceptions(std::ios::goodbit); in.open(filename, std::ios::binary | std::ios::in); if (const auto error = std::make_error_code(get_stream_error_code())) { return error; } if (not in.is_open()) { // Unknown error, so 'state_not_recoverable' is assumed. return std::make_error_code(std::errc::state_not_recoverable); } return {}; } inline void binary_ifstream::close() { in.close(); } inline std::error_code binary_ifstream::skip(size_type count) { in.ignore(count); return std::make_error_code(get_stream_error_code()); } template std::error_code binary_ifstream::skip() { return skip(sizeof(T)); } template std::error_code binary_ifstream::skip() { return skip(sizeof(T)); } inline std::error_code binary_ifstream::read(std::span buffer) { in.read(buffer.data(), static_cast(buffer.size())); return std::make_error_code(get_stream_error_code()); } template std::error_code binary_ifstream::read(T& integer) { std::array buffer; if (const auto error = read(buffer)) { return error; } if constexpr (std::endian::native != E) { std::reverse(buffer.begin(), buffer.end()); } // Use memcpy to avoid UB. std::memcpy(static_cast(&integer), buffer.data(), buffer.size()); return {}; } template std::error_code binary_ifstream::read_ieee754(T& floating_point) { static_assert(std::numeric_limits::is_iec559); std::array buffer; if (const auto error = read(buffer)) { return error; } if constexpr (std::endian::native != E) { std::reverse(buffer.begin(), buffer.end()); } // Use memcpy to avoid UB. std::memcpy(static_cast(&floating_point), buffer.data(), buffer.size()); return {}; }