2

I noticed that although the standard library has I/O manipulators to print numbers in either decimal, hexadecimal, or octal form (std::dec, std::hex, std::oct), it does not have one for binary.

How would I go about implementing an analogous binary I/O manipulator?


Anti-duplicate-marking section: I am quite dissapointed that I have to do this, but my questions almost always get marked as duplicates of completely different and unhelpful questions. Perhaps this will help people stay away from false duplicates and find the real duplicates, if they do exist.

  • I know how to print a number's binary representation. That is a trivial task for anyone who knows how numbers are stored in a computer, and not the purpose of this question (i.e. implementing std::string to_binary(T value), void print_binary(T value), ...).
  • I know that it is easy for anyone who has memorized the hexadecimal values to convert from hex to binary in their head. But any time you introduce human work, you introduce human capacity for error. Always best to avoid it if possible.
  • I realize there are rules about what you are allowed to do with respect to extending the standard library, though I don't know the inner workings of streams and manipulators well enough to know exactly what's allowed in this case. I'm not looking for a cop-out "you can't do that" answer - if the exact analog can't be created, then give me the cleanest allowed solution you can think of that is allowed.
Apollys supports Monica
  • 2,938
  • 1
  • 23
  • 33
  • Possible duplicate of [C++ custom stream manipulator that changes next item on stream](https://stackoverflow.com/questions/799599/c-custom-stream-manipulator-that-changes-next-item-on-stream) – Phil M Mar 15 '19 at 01:19
  • Definitely looks like a starting point. I'm working from that now but getting from there to here is definitely a non-trivial extension. – Apollys supports Monica Mar 15 '19 at 01:39
  • Related: https://stackoverflow.com/questions/7349689/how-to-print-using-cout-the-way-a-number-is-stored-in-memory – Amadeus Mar 15 '19 at 02:08
  • I think `bitset` is definitely a reasonable consideration given how hacky my current approach is. Only issue I see is they are templating it with a hard-coded size, hopefully this isn't necessary. – Apollys supports Monica Mar 15 '19 at 02:12
  • Confirmed hard-coding size is not necessary for bitset, can template with `8 * sizeof(var)`. – Apollys supports Monica Mar 15 '19 at 02:19

2 Answers2

0

Here is what I have thrown together so far, it still has many problems and I don't really understand what is going on. I have just copied and modified the solution to the question Phil linked.

#include <ios>
#include <iostream>
#include <locale>

int geti() { 
    static int i = std::ios_base::xalloc();
    return i;
}

std::ostream& bin_manip(std::ostream& os) {
  os.iword(geti()) = 1;
  return os;
}

std::ostream& dec_manip(std::ostream& os) {
  os.iword(geti()) = 0; 
  return os;
}

struct my_num_put : std::num_put<char> {
  iter_type do_put(iter_type out, std::ios_base& str, char_type fill, long v) const {
    bool binary_flag = str.iword(geti());
    if (binary_flag) {
      size_t width = 8 * sizeof(v);
      for (size_t i = width - 1; i < width; --i) {
        long bit = (((1 << i) & v) >> i) & 1;
        out = std::num_put<char>::do_put(out, str, fill, bit);
      }
      return out;
    }
    else {
      return std::num_put<char>::do_put(out, str, fill, v);
    }
  } 

  /*
  iter_type do_put(iter_type s, std::ios_base& f, char_type fill, unsigned long v) const { 
    return std::num_put<char>::do_put(s, f, fill, v + f.iword(geti())); 
  }
  */ 
};

int main() {
  std::cout.imbue(std::locale(std::locale(), new my_num_put));  // memory leak?
  int v1 = 10;
  long v2 = 11;
  std::cout << bin_manip << v1 << std::endl << v2 << std::endl;
  std::cout << dec_manip << v1 << std::endl << v2 << std::endl;

  return 0;
}

The output is the following:

0000000000000000000000000000000000000000000000000000000000001010
0000000000000000000000000000000000000000000000000000000000001011
10
11

The main problem I see here is code duplication when dealing with various types. As is, I just worked with the do_put function that takes a value of type long, which unfortunately prints out int values much wider than they should be. I tried to template the function and it nullified the effect of the manipulator entirely, simply printing out 10 and 11 rather than their binary representations.

Another issue is that I'm not sure what the best way to write each 1 and 0 to the stream is. For now I am writing them as longs which seems problematic, I'd really want to write them as single characters.

Finally, I'm not sure if the new is creating a memory leak, but valgrind is telling me it doesn't.

Apollys supports Monica
  • 2,938
  • 1
  • 23
  • 33
  • The usual way of avoiding the leak is to add `static my_num_put singleton;` to the class and then use `&my_num_put::singleton` instead of `new my_num_put` -- create a single static instance of the facet and (re)use that as often as needed, as it does not contain any state. – Chris Dodd Mar 15 '19 at 02:22
0

You could refer to this https://en.cppreference.com/w/cpp/io/manip/hex

  // Note: there is no I/O manipulator that sets up a stream to print out
  // ` numbers in binary format (e.g. bin). If binary output is necessary
  // ` the std::bitset trick can be used:

  std::cout << "The number 42 in binary:  " << std::bitset<8>{42}
      << std::endl;

  // output : The number 42 in binary:  00101010