1

According to these answers a buffer of bytes should be unsigned char, either because of convention or maybe the padding guarantees, I'm not sure. I have a function that looks something like:

saveDataToFile(const unsigned char* data, size_t size);

I find that I keep having to cast when I have a vector of char or an std::string or a string literal or something, and my code ends up looking like:

const char* text = "text";
saveDataToFile(text, 4); // Argument of const char* is incompatible with parameter of type const unsigned char*
saveDataToFile(reinterpret_cast<const unsigned char*>(text), 4);

Is there a way to avoid doing this all the time? Someone once mentioned to make my function take const char* instead of unsigned, but that doesn't really as then I'd have to cast the other way. For example std::string has .c_str() and .data() that return signed and unsigned. I also thought about taking void*, maybe that's the best way?

Zebrafish
  • 11,682
  • 3
  • 43
  • 119
  • So just overload the function depending on type of argument. – KamilCuk Oct 01 '20 at 16:21
  • 1
    Why don't you make it so the argument is `void*`? And then cast again inside the function, if you need to. – user253751 Oct 01 '20 at 16:35
  • @user253751 Yes, maybe taking a void* is best. I can't imagine needing to do anything with it like incrementing the pointer – Zebrafish Oct 01 '20 at 16:38
  • In modern C++, a byte should be `uint8_t` not `unsigned char`. The `char` type is based on range, whereas the `uint8_t` is exactly 8-bits. – Thomas Matthews Oct 01 '20 at 17:43
  • IMHO, the casting of `uint8_t *` to `char *` or `unsigned char *` to `char *` is only to please the compiler and reduce the warnings. Usually, these all have the same meaning. – Thomas Matthews Oct 01 '20 at 17:44
  • In C++17, a byte should be `std::byte` from `` which is an `enum class byte : unsigned char{};` – Eljay Oct 01 '20 at 18:33

1 Answers1

2

Perhaps the simplest way, as you have suggested yourself, is to make the function's first argument a const void* and then cast that to whatever is needed inside the function. This way, you also avoid using a reinterpret_cast and can safely use a static_cast:

void saveDataToFile(const void* data, size_t size)
{
    const uint8_t* local = static_cast<const uint8_t*>(data);
    //.. do something with the cast pointer ...
}

int main()
{
    double dData = 33.3;
    int16_t sData = 42;
    char cData[] = "Hello, World!";

    saveDataToFile(&dData, sizeof(dData));
    saveDataToFile(&sData, sizeof(sData));
    saveDataToFile(cData, sizeof(cData));

    return 0;
}

A more "Pure C++" way (in some folks' eyes, maybe) would be to make a templated function. However, the disadvantages here are: (a) you will need a reinterpret_cast in this case; and (b) the compiler will (probably) generate separate function code for each of the different argument types used:

template<typename T>
void saveDataToFile(const T* data, size_t size)
{
    const uint8_t* local = reinterpret_cast<const uint8_t*>(data);
    //.. do something with the cast pointer ...
}
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83