3
\$\begingroup\$

I want to overload an operator (e.g. operator<<) with different types, but also change only 1 or 2 values in the function itself.

I want to get some fancy output of a vector with the primitive integer types. I have tried to create a template function like template<int width, typename T> operator<<... but this would need to set the width and typename explicitly with each call of the operator<< e.g.

I have a following minimal code example (which is not the shortest tbh):

main.cpp

#include <iostream>
#include <vector>

#include "utils.h"

using namespace std;

int main(int argc, char* argv[]) {
  vector<int8_t> vec_s_1 = {1, 2, 3, -1, -2, -123};
  vector<uint8_t> vec_u_1 = {1, 2, 3, 127, 128, 129};
  vector<int16_t> vec_s_2 = {1, 2, 3, -1, -2, -123};
  vector<uint16_t> vec_u_2 = {1, 2, 3, 127, 128, 129};
  vector<int32_t> vec_s_3 = {1, 2, 3, -1, -2, -123};
  vector<uint32_t> vec_u_3 = {1, 2, 3, 127, 128, 129};
  vector<int64_t> vec_s_4 = {1, 2, 3, -1, -2, -123};
  vector<uint64_t> vec_u_4 = {1, 2, 3, 127, 128, 129};

  cout << "vec_s_1: " << vec_s_1 << endl;
  cout << "vec_u_1: " << vec_u_1 << endl;
  cout << endl;
  cout << "vec_s_2: " << vec_s_2 << endl;
  cout << "vec_u_2: " << vec_u_2 << endl;
  cout << endl;
  cout << "vec_u_3: " << vec_u_3 << endl;
  cout << "vec_s_3: " << vec_s_3 << endl;
  cout << endl;
  cout << "vec_s_4: " << vec_s_4 << endl;
  cout << "vec_u_4: " << vec_u_4 << endl;

  return 0;
}

utils.h

#ifndef MODULO_SEQUENCE_UTILS_H
#define MODULO_SEQUENCE_UTILS_H

#include <vector>
#include <iomanip>
#include <ostream>
#include <sstream>

using namespace std;

template<typename T>
ostream& operator<<(ostream& os, const vector<T>& obj);

#endif // MODULO_SEQUENCE_UTILS_H

utils.cpp

#include "utils.h"

#define GET_FUNCTION(WIDTH, TYPENAME, MASK) \
template<> ostream& operator<<(ostream& os, const vector<TYPENAME>& obj) { \
  size_t size = obj.size(); \
  os << "["; \
  for (size_t i = 0; i < size; ++i) { \
    if (i > 0) { \
      os << ", "; \
    } \
    stringstream ss; \
    ss << "0x" << hex << uppercase << setw(WIDTH) << setfill('0') << (obj[i] & MASK); \
    os << ss.str(); \
  } \
  os << "]"; \
  return os; \
}

GET_FUNCTION(2, int8_t, 0xFF)
GET_FUNCTION(2, uint8_t, 0xFF)
GET_FUNCTION(4, int16_t, 0xFFFF)
GET_FUNCTION(4, uint16_t, 0xFFFF)
GET_FUNCTION(8, int32_t, 0xFFFFFFFF)
GET_FUNCTION(8, uint32_t, 0xFFFFFFFF)
GET_FUNCTION(16, int64_t, 0xFFFFFFFFFFFFFFFF)
GET_FUNCTION(16, uint64_t, 0xFFFFFFFFFFFFFFFF)

So far it is working like expected and the output is like I wanted to have it. It is solved by calling #define for each function separately (which is a really looking ugly code).

The output looks like this:

vec_s_1: [0x01, 0x02, 0x03, 0xFF, 0xFE, 0x85]
vec_u_1: [0x01, 0x02, 0x03, 0x7F, 0x80, 0x81]

vec_s_2: [0x0001, 0x0002, 0x0003, 0xFFFF, 0xFFFE, 0xFF85]
vec_u_2: [0x0001, 0x0002, 0x0003, 0x007F, 0x0080, 0x0081]

vec_u_3: [0x00000001, 0x00000002, 0x00000003, 0x0000007F, 0x00000080, 0x00000081]
vec_s_3: [0x00000001, 0x00000002, 0x00000003, 0xFFFFFFFF, 0xFFFFFFFE, 0xFFFFFF85]

vec_s_4: [0x0000000000000001, 0x0000000000000002, 0x0000000000000003, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFE, 0xFFFFFFFFFFFFFF85]
vec_u_4: [0x0000000000000001, 0x0000000000000002, 0x0000000000000003, 0x000000000000007F, 0x0000000000000080, 0x0000000000000081]

I need the different widths of the leading zeros in the output plus the mask for getting the exact hex number for each type.

My question is: How can/could I achieve the same result with only using templates?

\$\endgroup\$
3
\$\begingroup\$

The trick is to use a byte_repeater like here.

template<typename T, std::size_t count, int byte>
struct byte_repeater;

template<typename T, int byte>
struct byte_repeater<T, 1, byte> {
    static constexpr T value = byte;
};

template<typename T, std::size_t count, int byte>
struct byte_repeater {
    static constexpr auto value = (byte_repeater<T, count-1, byte>::value << CHAR_BIT) | byte;
};

Add a convenient function make_mask

template<typename T, int mask>
struct make_mask {
    static constexpr auto value = byte_repeater<typename std::make_unsigned<T>::type, sizeof(T), mask>::value;
};

And then the ostream<< operator becomes:

template<typename T>
std::ostream& operator<<(std::ostream& os, const std::vector<T>& obj)
{
    static constexpr auto width = sizeof(T) * 2;
    static constexpr auto mask = make_mask<T,0xFF>::value;
    os << "[";
    std::for_each(obj.begin(), obj.end() - 1, [&os](const auto& elem) {
        os << "0x" << std::hex << std::uppercase << std::setw(width) << std::setfill('0') << (elem & mask) << ", ";
    });
    os << "0x" << std::hex << std::uppercase << std::setw(width) << std::setfill('0') << (obj.back() & mask);
    os << "]";
    return os;
}

LIVE DEMO

\$\endgroup\$
  • \$\begingroup\$ I found out that I need an unsigned type for the 3rd template struct. Can you explain to me, why in the 3rd template struct instead of the use of auto using the type T is not returning the desired output but instead using T2 = typename make_unsigned<T>::type; is working (static constexpr T2 value = ...), why? \$\endgroup\$ – PiMathCLanguage Jun 30 at 8:59

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.