4
\$\begingroup\$

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
\$\endgroup\$

1 Answer 1

5
\$\begingroup\$

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 }; }
\$\endgroup\$
9
  • \$\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
    Commented 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
    Commented 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\$ Commented Jun 2, 2018 at 11:40
  • \$\begingroup\$ @Spacemoose I elaborated in an edit. \$\endgroup\$ Commented 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 does os.operator<<(uint), which constructs the sentry. \$\endgroup\$
    – indi
    Commented Jun 2, 2018 at 23:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.