I'm trying to make a simple library for de/serialization in c++, but I know it can be tricky to implement, so I'd really like to have my code reviewed to see if there's anything that stands out and/or can be improved. [Here](https://gitlab.com/Nitr4m12/simple-binaryio) is the full repo containing all the code. 

**binaryio/reader.h**:
```
#include <memory>
#include <span>
#include <stdexcept>

#include "binaryio/interface.h"
#include "binaryio/swap.h"

#ifndef BINARYIO_READER_H
#define BINARYIO_READER_H

namespace binaryio {

class BinaryReader : IBinaryIO {
public:
    BinaryReader(void* const begin, void* const end)
        : m_begin(reinterpret_cast<char*>(begin)),
          m_end(reinterpret_cast<char*>(end)),
          m_current(reinterpret_cast<char*>(begin)){};

    BinaryReader(void* const begin, void* const end, const endian& endianness)
        : m_begin{reinterpret_cast<char*>(begin)},
          m_end{reinterpret_cast<char*>(end)},
          m_current{reinterpret_cast<char*>(begin)}, m_endian{endianness} {};

    BinaryReader(void* const begin, const std::ptrdiff_t& size)
        : m_begin{reinterpret_cast<char*>(begin)}, m_end{m_begin + size},
          m_current{reinterpret_cast<char*>(begin)} {};

    BinaryReader(void* const begin, const std::ptrdiff_t& size,
                 const endian& endianness)
        : m_begin{reinterpret_cast<char*>(begin)}, m_end{m_begin + size},
          m_current{reinterpret_cast<char*>(begin)}, m_endian{endianness} {};

    void seek(std::ptrdiff_t offset) override {
        if (m_begin + offset > m_end)
            throw std::out_of_range("out of bounds seek");

        m_current = m_begin + offset;
    }

    size_t tell() const override {
        size_t offset{static_cast<size_t>(m_current - m_begin)};
        return offset;
    }

    template <typename T>
    T read() {
        if (m_current + sizeof(T) > m_end)
            throw std::out_of_range("out of bounds read");

        T val = *(T*)m_current;
        swap_if_needed_in_place(val, m_endian);
        m_current += sizeof(T);
        return val;
    }

    std::string read_string(size_t max_len = 0) {
        if (m_current + max_len > m_end || max_len == 0)
            max_len = m_end - m_current;

        return {m_current, strnlen(m_current, max_len)};
    }

    template <typename T>
    std::span<T> read_many(int count) {
        if (m_current + sizeof(T) * count > m_end)
            throw std::out_of_range("out of bound read");

        std::span<T> vals{{}, count};
        for (int i{0}; i < count; ++i) {
            vals[i] = *(T*)m_current;
            swap_if_needed_in_place(vals[i], m_endian);
            m_current += sizeof(T);
        }
        return vals;
    }

    endian endianness() { return m_endian; }

    void set_endianness(endian new_endian) { m_endian = new_endian; }

    void swap_endianness() {
        if (m_endian == endian::big)
            m_endian = endian::little;
        else
            m_endian = endian::big;
    }

private:
    char* m_begin;
    char* m_end;
    char* m_current;
    endian m_endian{endian::native};
};
} // namespace binaryio
#endif
```

**binaryio/writer.h**:
```
#include <memory>
#include <span>
#include <vector>

#include "binaryio/align.h"
#include "binaryio/interface.h"
#include "binaryio/swap.h"

#ifndef BINARYIO_WRITER_H
#define BINARYIO_WRITER_H

namespace binaryio {
class BinaryWriter : IBinaryIO {
public:
    // Based on
    // https://github.com/zeldamods/oead/blob/master/src/include/oead/util/binary_reader.h
    BinaryWriter() = default;

    BinaryWriter(endian byte_order) : m_endian{byte_order} {};

    std::vector<uint8_t> finalize() { return std::move(m_storage); }

    void seek(std::ptrdiff_t offset) override { m_offset = offset; };
    size_t tell() const override { return m_offset; }

    void write_bytes(const uint8_t* data, size_t size) {
        std::span<const uint8_t> bytes{data, size};

        if (m_offset + bytes.size() > m_storage.size())
            m_storage.resize(m_offset + bytes.size());

        std::memcpy(&m_storage[m_offset], bytes.data(), bytes.size());
        m_offset += bytes.size();
    };

    template <typename T,
              typename std::enable_if_t<!std::is_pointer_v<T> &&
                                        std::is_trivially_copyable_v<T>>* = nullptr>
    void write(T value) {
        swap_if_needed_in_place(value, m_endian);
        write_bytes(reinterpret_cast<const uint8_t*>(&value), sizeof(value));
    }

    void write(std::string_view str) {
        write_bytes(reinterpret_cast<const uint8_t*>(str.data()), str.size());
    }

    void write_null() { write<uint8_t>(0); }

    void write_cstr(std::string_view str) {
        write(str);
        write_null();
    }

    void align_up(size_t n) { seek(AlignUp(tell(), n)); }

private:
    std::vector<uint8_t> m_storage;
    size_t m_offset{0};
    endian m_endian{endian::native};
};
} // namespace binaryio

#endif
```