Skip to content

Instantly share code, notes, and snippets.

@daniel-j-h

daniel-j-h/io.cc Secret

Last active November 17, 2016 22:44
Show Gist options
  • Save daniel-j-h/af3b08a2a569d91d638006cbbe3fff83 to your computer and use it in GitHub Desktop.
Save daniel-j-h/af3b08a2a569d91d638006cbbe3fff83 to your computer and use it in GitHub Desktop.
#include <cstdint>
#include <cstdio>
#include <stdexcept>
#include <string>
#include <type_traits>
// Stream Concepts - Where to read bytes from and write bytes to.
// Models for this could be a readable file, writable memory, etc.
struct InputStream {
void ReadBytes(void* into, std::int64_t size);
};
struct OutputStream {
void WriteBytes(const void* from, std::int64_t size);
};
// Compile time stream concept checks.
// Usage example: static_assert(IsInputStream<T>(), "");
template <typename T, typename = void>
struct IsInputStream : std::false_type {};
template <typename T>
struct IsInputStream<T, decltype((void)std::declval<T>().ReadBytes(nullptr, 0))> : std::true_type {};
template <typename T, typename = void>
struct IsOutputStream : std::false_type {};
template <typename T>
struct IsOutputStream<T, decltype((void)std::declval<T>().WriteBytes(nullptr, 0))> : std::true_type {};
template <typename T>
struct IsInputOutputStream : std::integral_constant<bool, IsInputStream<T>() && IsOutputStream<T>()> {};
// Error Handling for Streams
struct StreamError : std::runtime_error {
using std::runtime_error::runtime_error;
};
struct InputStreamError : StreamError {
using StreamError::StreamError;
};
struct OutputStreamError : StreamError {
using StreamError::StreamError;
};
// Protocol Concepts - How to encode from types to bytes and decode from bytes to types.
// This could be a binary encoding / decoding, or e.g. using Cap'n Proto.
template <typename OutputStream> struct Encoder {
Encoder(OutputStream& outStream);
template <typename T>
void encode(const T& value);
};
template <typename InputStream> struct Decoder {
Decoder(InputStream& inStream);
template <typename T>
void decode(T& into);
};
// Compile time protocol concept checks.
// Usage example: static_assert(IsEncodeable<BinaryEncoder, uint32_t>(), "");
template <typename Protocol, typename T, typename = void>
struct IsEncodeable : std::false_type {};
template <typename Protocol, typename T>
struct IsEncodeable<Protocol, T, decltype((void)std::declval<Protocol>().encode(std::declval<T>()))> : std::true_type {};
template <typename Protocol, typename T, typename = void>
struct IsDecodeable : std::false_type {};
template <typename Protocol, typename T>
struct IsDecodeable<Protocol, T, decltype((void)std::declval<Protocol>().decode(std::declval<T&>()))> : std::true_type {};
template <typename Protocol, typename T>
struct IsEncodeableDecodeable : std::integral_constant<bool, IsEncodeable<Protocol, T>() && IsDecodeable<Protocol, T>()> {};
// Error Handling for Reader / Writer
struct ProtocolError : std::runtime_error {
using std::runtime_error::runtime_error;
};
struct EncodeError : ProtocolError {
using ProtocolError::ProtocolError;
};
struct DecodeError : ProtocolError {
using ProtocolError::ProtocolError;
};
// Serialize and Deserialize functions for type deduction: no need to specify T or Protocol.
// Usage: Serialize(object, encoder);
template <typename T, typename Protocol>
void Serialize(const T& value, Protocol& protocol) {
static_assert(IsEncodeable<Protocol, T>(), "Unable to encode T with this Protocol");
protocol.encode(value);
}
template <typename T, typename Protocol>
void Deserialize(T& value, Protocol& protocol) {
static_assert(IsDecodeable<Protocol, T>(), "Unable to decode T with this Protocol");
protocol.decode(value);
}
// Example: writing to stdout encoded as binary
// The StdoutStream knows how to write bytes to stdout
struct StdoutStream {
void WriteBytes(const void* from, std::int64_t size) {
if (!from || size == 0)
return;
auto first = (std::uint8_t*)from;
auto last = first + size;
for (; first != last; ++first)
std::putchar(*first);
}
};
// The BinaryEncoder knows how to encode types into binary
template <typename OutputStream>
struct BinaryEncoder {
static_assert(IsOutputStream<OutputStream>(), "Model for OutputStream concept required");
BinaryEncoder(OutputStream& outStream) : outStream{outStream} {}
// For all memcpy-able types we dump byte-wise
template <typename T>
typename std::enable_if<std::is_trivially_copyable<T>::value>::type
encode(const T& value) {
outStream.WriteBytes(&value, sizeof(T));
}
// For std::string we first write out the string's size and then the data
template <typename T>
typename std::enable_if<std::is_same<T, std::string>::value>::type
encode(const T& value) {
auto len = (std::uint64_t)value.size();
encode(len);
outStream.WriteBytes(value.data(), value.size());
}
OutputStream& outStream;
};
// Usage: ./a.out > out.bin; xxd out.bin
int main() {
std::string input{"Hello"};
StdoutStream outStream;
BinaryEncoder<StdoutStream> binaryWriter{outStream};
Serialize(input, binaryWriter);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment