-
-
Save daniel-j-h/af3b08a2a569d91d638006cbbe3fff83 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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