How do I get my Hex String to include intermediate zeroes in C++?

110 views Asked by At

I am trying to output a UUID stored as a vector of bytes as a Hex String with dashes. When any individual byte has a leading zero, my output string skips it. I am using std::setw(2) and am still getting this behavior.

I tried the following code:

#include <iomanip>
#include <iostream>

std::ostream& operator<<(std::ostream &os, const Id &id) {
  std::ios oldState(nullptr);
  oldState.copyfmt(os);
  os << std::hex << std::uppercase << std::setfill('0') << std::setw(2);
  for (int i = 0; i < 16; i++) {
    os << (int)id.id_[i];
    if (i == 3 || i == 5 || i == 7 || i == 9) os << '-';
  }
  os.copyfmt(oldState);
  return os;
}

My expected output would be something like:

01020304-0102-0304-0506-AA0F0E0D0C0B

and instead I get:

1234-12-34-56-AAFEDCB

I'm obviously doing something wrong here. I've tried flushing the buffer and inserting the dashes after the fact and still no luck.

2

There are 2 answers

0
tbxfreeware On BEST ANSWER

As was explained in the comments, std::setw is a one-time manipulator. It works on the next output item only. After that, the stream width is reset to 0.

The fix is easy enough: Move setw into the loop.

For demonstration purposes, I created struct Id.

// main.cpp
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <vector>
#include <initializer_list>

struct Id
{
    Id(std::initializer_list<std::uint8_t> l)
        : id_{ l }
    {}
    std::vector<std::uint8_t> id_;
};

std::ostream& operator<<(std::ostream& os, const Id& id) {
    std::ios oldState(nullptr);
    oldState.copyfmt(os);
    os << std::hex << std::uppercase << std::setfill('0');
    for (int i = 0; i < 16; i++) {
        os << std::setw(2) << (int)id.id_[i];  // <===== setw goes here
        if (i == 3 || i == 5 || i == 7 || i == 9) os << '-';
    }
    os.copyfmt(oldState);
    return os;
}
int main()
{
    Id id{
        0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 
        0x05, 0x06, 0xAA, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B
    };
    std::cout << id << '\n';
    return 0;
}

Output:

01020304-0102-0304-0506-AA0F0E0D0C0B
0
Jakkapong Rattananen On

I think create function for convert then call it in overload is more reusable.

here code

#include <cstdint>
#include <iostream>
#include <array>
#include <string>

struct Id
{
    std::array<uint8_t, 16> bytes;// std::array is same as normal array but more useful feature.
};

std::string uuid2rfc4122(const Id& uuid)
{
    static char char2hex_table[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
    std::string str;
    str.reserve(36);//this prevent for reallocate memory from push_back 

    int pos = 0;
    for (auto byte : uuid.bytes) { //this intend to do not use reference. Reference is actually pointer so their size is 8 bytes in 64bit system. it has overhead if we store reference pointer instead of value.
        //1 byte represent by 2 hex character
        str.push_back(char2hex_table[byte >> 4]); //0b1111'xxxx We don't need x. After shift value = 0b1111
        str.push_back(char2hex_table[byte & 0x0f]);//0bxxxx'1111 After bitwiseAnd value = 0b0000'1111
        ++pos;
        if (pos == 4 || pos == 6 || pos == 8 || pos == 10) {
            str.push_back('-');
        }
    }
    return str;
}

std::ostream& operator<<(std::ostream& os, const Id& id) {
    os << uuid2rfc4122(id);
    return os;
}