like, you can manually do what rust does automatically for you with a trait,
- create a concept to check the type fulfills your interface requirements
namespace trait {
template<typename T>
concept bytestream =
requires( T& stream, void* buffer, usize size) { { stream.read_bytes(buffer, size) } -> std::same_as<result<void>>; } and
requires( T& stream, usize position) { { stream.seek(position) } -> std::same_as<result<void>>; } and
requires(const T& stream) { { stream.position() } -> std::same_as<result<usize>>; }
;
}
- create a virtual base class for this interface
struct model {
model() = default;
virtual ~model() = default;
virtual auto read_bytes(void* buffer, usize size) -> result<void> = 0;
virtual auto seek(usize position) -> result<void> = 0;
virtual auto position() const -> result<usize> = 0;
};
- create a wrapper that stores any T that satisfies the concept, and implements the virtual functions by calling the corresponding function on the T:
template<typename T>
struct impl final : model {
template<typename X>
impl(X&& x) : reader{std::forward<X>(x)} {}
auto read_bytes(void* buffer, usize size) -> result<void> override { return reader.read_bytes(buffer, size); }
auto seek(usize position) -> result<void> override { return reader.seek(position); }
auto position() const -> result<usize> override { return reader.position(); }
T reader;
};
- wrap this wrapper in a wrapper that stores a
unique_ptr<model> and forwards all the functions to that one