c++c++17std-byte

Are bit flags an intended use of std::byte?


As of C++17 there are the following types representing an 8-bit unit of storage:

  1. char, unsigned char
  2. int8_t, uint8_t
  3. std::byte

As I understand it the intent is that char's should now be used when the byte is actually representing a character, for example, in a string. int8_t should be used when the byte is representing a numeric value that happens to be an integer only needing 8-bits of storage. And std::byte should be used for everything else, essentially as a unit of binary data -- if you read a binary file from the file system, say.

But what about bit fields? bit flags ... whatever you want to call them. Like say you are writing a random maze generator and you want to represent the maze as an std::vector of bytes where each byte contains flags as bits specifying which walls of a cell are present. If you wanted to convert back and forth between a struct of bools and byte representation of such a cell you would have to use something like the following syntax as far as I call tell:

const std::byte kTop{0b0001};
const std::byte kRight{0b0010};
const std::byte kBottom{0b0100};
const std::byte kLeft{0b1000};

Maze::Cell::Cell(std::byte b) :
    top( static_cast<bool>(b & kTop) ),
    right( static_cast<bool>(b & kRight) ),
    bottom( static_cast<bool>( b & kBottom) ),
    left( static_cast<bool>(b & kLeft) )
{
}

std::byte Maze::Cell::toByte() const
{
    return (top ? kTop : std::byte{0}) |
        (right ? kRight : std::byte{0}) |
        (bottom ? kBottom : std::byte{0}) |
        (left ? kLeft : std::byte{0});
}

This syntax seems to a little verbose to me, having to explicitly cast to bool, having no short literal for std::byte{0}, etc.

Is there some simpler syntax for this sort of thing or is std::byte the wrong choice of type?


Solution

  • Like say you are writing a random maze generator and you want to represent the maze as an std::vector of bytes where each byte contains flags as bits specifying which walls of a cell are present.

    Consider using a std::vector of std::bitset<4> for that. You can access the individual bits using normal array syntax via its operator[]:

    #include <bitset>
    
    using wallSet = std::bitset<4>;
    
    const std::size_t kTop = 0;
    const std::size_t kRight = 1;
    const std::size_t kBottom = 2;
    const std::size_t kLeft = 3;
    
    struct Cell
    {
        wallSet walls;
    
        Cell(wallSet);
        Cell(std::byte);
    
        bool top() const;
        void top(bool);
    
        bool right() const;
        void right(bool);
    
        bool bottom() const;
        void bottom(bool);
    
        bool left() const;
        void left(bool);
    
        std::byte toByte() const;
    }
    
    Cell::Cell(wallSet ws)
        : walls(ws)
    {
    }
    
    Cell::Cell(std::byte b)
        : Cell(wallSet(static_cast<unsigned long>(b)))
    {
    }
    
    bool Cell::top() const { return walls[kTop]; }
    void Cell::top(bool value) { walls[kTop] = value; }
    
    bool Cell::right() const { return walls[kRight]; }
    void Cell::right(bool value) { walls[kRight] = value; }
    
    bool Cell::bottom() const { return walls[kBottom]; }
    void Cell::bottom(bool value) { walls[kBottom] = value; }
    
    bool Cell::left() const { return walls[kLeft]; }
    void Cell::left(bool value) { walls[kLeft] = value; }
    
    std::byte Cell::toByte() const
    {
        return static_cast<std::byte>(walls.to_ulong());
    }