# Templated byte streaming

I often have the need to take some type and stream it to something as a sequence of character byte values, so I came up with this to accomplish the task:

#include <iostream>
#include <iterator>

/// This provides a way of getting the byte representation of an
/// object of underlying type T.
///
/// This is an alternative to memcopying bytes into a string, stream,
/// character array...
template<typename inT, typename outT = char>
struct byte_streamer
{
byte_streamer(inT ref)
: data(ref)
{}
const inT data;
outT placeholder;
};

template<typename inT, typename outT>
std::ostream& operator<<(std::ostream& strm, const byte_streamer<inT, outT> val)
{
std::copy(reinterpret_cast<const char*>(&val.data),
reinterpret_cast<const char*>(&val.data) + sizeof(inT),
std::ostream_iterator<decltype(val.placeholder)>(strm, ""));
return strm;
}

int main()
{
std::cout << "Here's the bytes of 42 copied directly: " << byte_streamer(42) << std::endl;
std::cout << "Here's the bytes of 42 copied as their numerical values: "
<< byte_streamer<int, int>(42);
}


You can run it online on Coliru or just look at the output here:

Here's the bytes of 42 copied directly: *
Here's the bytes of 42 copied as their numerical values: 42000


Well, there are a few points:

1. You make a copy of the object you output. That can be costly, impossible, or have undesirable side-effects. Use a constant reference instead.
2. There are essentially two modes for your byte_streamer: Output the raw bytes, or output as decimal values for each byte.
The latter is broken as each byte is variable-width (1-3 digits for octets), and there is no separator.
3. You use the member placeholder only to convey the type outT to other code. Why not use a typedef instead, avoiding inefficiency and painful contortions?
4. Only use std::endl if you want to flush the stream. Otherwise, using '\n' and avoiding that extra-work is simply far more efficient.
5. It's really unusual not to end the programs output with a line-break. Wonder where the prompt will end up if called from a terminal...

Imho, the only useful outputs would be hex-dump and raw-bytes.

class dump_hex_t {
const void* p = nullptr;
std::size_t s = 0;
public:
constexpr dump_hex_t() noexcept = default;
constexpr dump_hex_t(const void* p, std::size_t s) noexcept : p(p), s(s) {}

template <class CharT, class Traits>
friend auto& operator<<(std::basic_ostream<CharT, Traits>& os, dump_hex_t x) {
... // Dump as hex-digits however you like
}
};

template <class T>
constexpr dump_hex_t dump_hex(const T& x) noexcept
{ return { std::addressof(x), sizeof x }; }

template <class T>
constexpr std::string_view dump_raw(const T& x) noexcept
{ return { (const char*)std::addressof(x), sizeof x }; }

• Why the unnecessary default constructor - the class's only purpose is to be used by dump_hex(), right? And why manually construct the sentry in the inserter? – indi Jun 2 '18 at 1:18
• Thank you for reviewing my code. Regarding your points: 1) yup. 2)ahhh... now I realize in my attempt to create a minimally sized reviewable example, I did a poor example explaining what I need to accomplish -- probably because of a poor understanding thereof. I'll have to meditate on that. 3) I don't follow what you mean by using a typedef instead. Can you elaborate? – Spacemoose Jun 2 '18 at 6:34
• @indi: I did the default-ctor just because I could. And I manually construct the sentry because that's implementing a formatted stream-inserter, and those should do that. – Deduplicator Jun 2 '18 at 11:40
• @Spacemoose I elaborated in an edit. – Deduplicator Jun 2 '18 at 22:57
• There is no need to construct the sentry object if you're just using the stream's functions; they all do it internally anyway. Presumably the "..." will be something like copy_n(static_cast<uchar const*>(x.p), x.s, ostream_inserter<uint>{os, " "});, which does os.operator<<(uint), which constructs the sentry. – indi Jun 2 '18 at 23:51