208 lines
4.2 KiB
C++
208 lines
4.2 KiB
C++
#pragma once
|
|
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <span>
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
|
|
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<char_type> buffer);
|
|
|
|
template<std::endian E, std::integral T>
|
|
[[nodiscard]] std::error_code read(T& integer);
|
|
|
|
template<std::endian E, std::floating_point T>
|
|
[[nodiscard]] std::error_code read_ieee754(T& floating_point);
|
|
|
|
[[nodiscard]] inline std::error_code skip(size_type count);
|
|
|
|
template<std::integral T>
|
|
[[nodiscard]] std::error_code skip();
|
|
|
|
template<std::floating_point T>
|
|
[[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<char_type> 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::floating_point T>
|
|
std::error_code binary_ifstream::skip()
|
|
{
|
|
return skip(sizeof(T));
|
|
}
|
|
|
|
template<std::integral T>
|
|
std::error_code binary_ifstream::skip()
|
|
{
|
|
return skip(sizeof(T));
|
|
}
|
|
|
|
inline std::error_code binary_ifstream::read(std::span<char_type> buffer)
|
|
{
|
|
in.read(buffer.data(), static_cast<size_type>(buffer.size()));
|
|
return std::make_error_code(get_stream_error_code());
|
|
}
|
|
|
|
template<std::endian E, std::integral T>
|
|
std::error_code binary_ifstream::read(T& integer)
|
|
{
|
|
std::array<char_type, sizeof(T)> 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<void*>(&integer), buffer.data(), buffer.size());
|
|
|
|
return {};
|
|
}
|
|
|
|
template<std::endian E, std::floating_point T>
|
|
std::error_code binary_ifstream::read_ieee754(T& floating_point)
|
|
{
|
|
static_assert(std::numeric_limits<T>::is_iec559);
|
|
|
|
std::array<char_type, sizeof(T)> 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<void*>(&floating_point), buffer.data(), buffer.size());
|
|
|
|
return {};
|
|
}
|