27

C++14 adds ability to use binary literals by typing 0b prefix for the value:

int v = 0b1111; // 15 in decimal

But there is no std::bin manipulator for streams like std::hex or std::oct. So I need to use e.g. std::bitset for printing purpose:

std::cout << std::bitset<4>(v) << "\n";

Has it been proposed or considered? If so, what's the status of the idea?

MSalters
  • 173,980
  • 10
  • 155
  • 350
αλεχολυτ
  • 4,792
  • 1
  • 35
  • 71

1 Answers1

10

As far as I know there was no proposal submitted to add a formatting flag to add binary formatting and/or a manipulator std::bin. You can check the proposals at http://www.open-std.org/jtc1/sc22/wg21/docs/papers/. I'm pretty sure the proposal to add binary literals did not add this facility (a quick search revealed N3472 but I'm not sure if this is the latest version of the paper).

From a technical point of view it may not be entirely easy to add! The various flags are normally all stored in just one word in the stream class and there are various reasons to use up all the bits. The existing three settings (std::ios_base::oct, std::ios_base::dec, std::ios_base::hex) can be nicely stored in just 2 bits. Of course, the three values would leave one value open except that this value is typically taken for the default setting, i.e., not fixing the base when reading. As a result it may be necessary to change the layout of the stream classes or to make processing less efficient (e.g., by somehow using an iword() to store the additional possibility of binary formatting). I haven't done the analysis whether there is an actual problem with any of the implementations (I know there is none for my implementation but I did use all the bits in a word if I recall correctly).

If you want to support binary formatting it is relatively easy to add via a custom std::num_put<char> facet. Below is a simple example. It doesn't deal with some of the formatting options which may be desirable like padding or digit separators:

#include <iostream>
#include <limits>
#include <locale>

class binary_num_put
    : public std::num_put<char> {
    template <typename T>
    iter_type common_put(iter_type out, std::ios_base& str, char_type fill,
                         T original, unsigned long long v) const {
        if (str.flags() & std::ios_base::basefield) {
            return this->std::num_put<char>::do_put(out, str, fill, original);
        }
        if (str.flags() & std::ios_base::showbase) {
            *out++ = '0';
            *out++ = str.flags() & std::ios_base::uppercase? 'B': 'b';
        }
        unsigned long long mask(1ull << (std::numeric_limits<unsigned long long>::digits - 1));
        while (mask && !(mask & v)) {
            mask >>= 1;
        }
        if (mask) {
            for (; mask; mask >>= 1) {
                *out++ = v & mask? '1': '0';
            }
        }
        else {
            *out++ = '0';
        }
        return out;
    }
    iter_type do_put(iter_type out, std::ios_base& str, char_type fill, long v) const {
        return common_put(out, str, fill, v, static_cast<unsigned long>(v));
    }
    iter_type do_put(iter_type out, std::ios_base& str, char_type fill, long long v) const {
        return common_put(out, str, fill, v, static_cast<unsigned long long>(v));
    }
    iter_type do_put(iter_type out, std::ios_base& str, char_type fill, unsigned long v) const {
        return common_put(out, str, fill, v, v);
    }
    iter_type do_put(iter_type out, std::ios_base& str, char_type fill, unsigned long long v) const {
        return common_put(out, str, fill, v, v);
    }
};

std::ostream& bin(std::ostream& out) {
    auto const& facet = std::use_facet<std::num_get<char>>(out.getloc());
    if (!dynamic_cast<binary_num_put const*>(&facet)) {
        std::locale loc(std::locale(), new binary_num_put);
        out.imbue(loc);
    }
    out.setf(std::ios_base::fmtflags(), std::ios_base::basefield);
    return out;
}

int main()
{
    std::cout << std::showbase << bin << 12345 << " "
              << std::dec << 12345 << "\n";
}
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • In all implementations I checked, dec, oct and hex are separate bits. And at least libstdc++ (and libc++ on 32+ bit platforms) seem to have plenty of spare bits, not sure about the others. – Marc Glisse Dec 28 '16 at 17:02
  • @MarcGlisse: i know that [my implementation](http://www.dietmar-kuehl.de/cxxrt/) does *not* use separate bits. Admittedly, it does store the base, i.e., it would be trivial to add a `std::bin` manipulator. My implementation folds a number of conditions into the same word to allow checking of multiple separate conditions with just one branch and one memory access (and it seems I actually still had one spare bit for 32 bit words). – Dietmar Kühl Dec 28 '16 at 17:26
  • The standard says that [`fmtflags` is a bitmask type and `dec`, `oct` and `hex` (among others) are its elements](https://timsong-cpp.github.io/cppwp/ios::fmtflags#1), meaning that [they must effectively represent separate bits](https://timsong-cpp.github.io/cppwp/bitmask.types#3) (because `bitand`-ing any two of them must yield zero). Of course, an implementation could choose to use a different internal representation, but that requires additional work to provide translation both ways. – T.C. Jan 01 '17 at 08:06
  • @T.C.: in my implementation the `fmtflags` are stored in the same word as a number of state bits. Doing so requires a few extra bitwise operations on [user] access to the flags. In return some checks require just one branch. – Dietmar Kühl Jan 01 '17 at 09:23