12
\$\begingroup\$

After reading this question Easy bitset I/O. I wanted to see if there was a simpler way of doing it.

This is how I would define a way of outputting std::bitset<N> to the stream and reading it back in. It works perfectly with current containers.

BitIO.h

// Include guards deliberately not included for simplicity.

#include <bitset>
#include <iostream>

template<int S>
class BitIO
{
    public:
    std::bitset<S>&     data;
    BitIO(std::bitset<S>& data)
        : data(data)
    {}

    friend std::ostream& operator<<(std::ostream& stream, BitIO const& value)
    {
        // Currently using just an integer.
        //
        // But we could easily take this a step further and use hex.
        // and pad with 0 to get an exact width (that way we don't need a
        // trailing space).
        //
        // Using a fixed width means we may potentially waste space but can
        // be made to waste the least amount of space possible quite easily.
        //
        // Amount of wasted space would be (4 - (S % 4)) % 4

        return stream << value.data.to_ullong() << " ";
    }
    friend std::istream& operator>>(std::istream& stream, BitIO& value)
    {
        // As the current output uses a white space separated
        // unsigned long long it is easy to read it back in using the
        // normal `operator>>`
        //
        // Would be just as simple to convert this function to use
        // a fixed width format to mirror the output. 
        unsigned long long tmp;
        if (stream >> tmp)
        {
            value.data  = std::bitset<S>(tmp);
        }
        return stream;
    }
};

main.cpp

#include "BitIO.h"
#include <vector>

int main()
{
    std::vector<std::bitset<16> >    values { std::bitset<16>(1), std::bitset<16>(2), std::bitset<16>(255) };

    std::copy(std::begin(values), std::end(values), std::ostream_iterator<BitIO<16>>(std::cout));


    std::bitset<16>  data { 129 };
    std::cout << BitIO<16>(data);

}
\$\endgroup\$
6
+100
\$\begingroup\$

The code is relatively short and well-written, so I didn't find many nits to pick. With that said, here's my review:

Include all needed files

Since std::ostream_iterator is declared in <iterator> that should be among the includes in main:

#include <iterator>

Use appropriate type for template parameter

The std::bitset template is defined as taking std::size_t rather than int, so it would make sense to define the BitIO template similarly:

template<std::size_t S>
class BitIO
{
    // ...
};

One can, of course, still abuse the interface by passing negative numbers but at least the user of the code is forewarned that they will be interpreted as std::size_t rather than as signed numbers.

Consider implementing a move constructor

At the moment, this potential usage cannot compile:

std::cout << BitIO<16>(std::bitset<16>{ 99 });

It's easily fixed by adding this constructor:

BitIO(std::bitset<S>&& data)
    : data(data)
{}

Maintain parallelism with existing practice

I was surprised to see spaces separating the outputs when none had been specified in the output ostream_iterator. Although having the space is arguably useful, the choice of separator (if any) should be left up to the user of the template instead of being hardcoded in the operator << function. I'd omit the trailing space to allow usage like this:

std::copy(std::begin(values), std::end(values), 
        std::ostream_iterator<BitIO<16>>(std::cout,":"));
\$\endgroup\$
  • \$\begingroup\$ The constructor could take its value by const reference and still work (have not tried). But that would make the move constructor redundant. \$\endgroup\$ – Martin York Dec 19 '14 at 20:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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