\$\begingroup\$
\$\endgroup\$
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
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Jun 1, 2018 at 15:11
1 Answer 1
\$\begingroup\$
\$\endgroup\$
9
Well, there are a few points:
- You make a copy of the object you output. That can be costly, impossible, or have undesirable side-effects. Use a constant reference instead.
- 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. - You use the member
placeholder
only to convey the typeoutT
to other code. Why not use a typedef instead, avoiding inefficiency and painful contortions? - Only use
std::endl
if you want to flush the stream. Otherwise, using'\n'
and avoiding that extra-work is simply far more efficient. - 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 }; }
answered Jun 1, 2018 at 16:25
-
\$\begingroup\$ 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? \$\endgroup\$indi– indi2018年06月02日 01:18:11 +00:00Commented Jun 2, 2018 at 1:18 -
\$\begingroup\$ 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? \$\endgroup\$Spacemoose– Spacemoose2018年06月02日 06:34:52 +00:00Commented Jun 2, 2018 at 6:34
-
\$\begingroup\$ @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. \$\endgroup\$Deduplicator– Deduplicator2018年06月02日 11:40:54 +00:00Commented Jun 2, 2018 at 11:40
-
\$\begingroup\$ @Spacemoose I elaborated in an edit. \$\endgroup\$Deduplicator– Deduplicator2018年06月02日 22:57:15 +00:00Commented Jun 2, 2018 at 22:57
-
\$\begingroup\$ 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 doesos.operator<<(uint)
, which constructs the sentry. \$\endgroup\$indi– indi2018年06月02日 23:51:06 +00:00Commented Jun 2, 2018 at 23:51
lang-cpp