3

I have a class defined like this:

class sco
{
private:

public:
    vector<uint8_t> data;

    sco(vector<uint8_t> data);
    ~sco();

};

Where the constructor is:

sco::sco(vector<uint8_t> data) {       
    this->data = data;
}

I then have a function which is declared like this:

void send(unsigned& id, char* data, char len);

My problem is that I need to pass the data of a sco member to it, but the difference of type and the pointer is confusing me. If I have a member newSco with some data in it, would it be reasonable to call this send function as send(someId, (char*)&(newSco.data.begin()), newSco.data.size() ); ? Please note, that the function send is for a microcontroller, and it takes in char type so I can't change that, and neither can I change uint8_t as that is the type which is coming in from a serial communication. I have wasted over 3 days trying to convert types to something mutual, just to reverse it back because it destroyed everything. I give up and I will no longer try to manipulate the types, as I just do not have that sort of time and just need it to work even if it is bad practice. I thought uint8_t and char are of the same size, so it shouldn't matter.

Tryb Ghost
  • 379
  • 1
  • 11

5 Answers5

6
send(someId, (char*)&(newSco.data.begin()), newSco.data.size() )

You almost had it with this, but not quite.

Here's why:

  • begin() gives you an iterator. You're taking the address of that iterator, so you're a level of indirection off. Using a C-style cast has masked what would otherwise be a type-related compilation error.

    We could write (char*)&*(newSco.data.begin()) instead to dereference the iterator then take the address of the resulting first element.

  • But if the container were empty, this is very broken. You can't dereference a thing that doesn't exist.

So now we try:

send(someId, (char*)&newSco.data[0], newSco.data.size() )

Unfortunately this is also not safe if the container is empty, since .data[0] also effectively dereferences an element that may not exist. Some argue that the implied &* "cancels it out", but that's controversial and I've never believed it.

If you ever move to C++11 or later, you can use the perfectly safe:

send(someId, (char*)newSco.data.data(), newSco.data.size() )

Otherwise, stick with &newSco.data[0] but skip the entire send call when newSco.data.size() is zero. Can't emphasise that enough.

The cast to char* is itself safe; you can freely interpret uint8_ts as chars in this manner; there's a special rule for it. I've used this pattern myself a few times.

But, as above, a C-style cast is less than ideal. Prefer a nice reinterpret. I'd also add some const for good measure (skip this if your MC's API doesn't permit it):

if (!newSco.data.empty())
{
   send(
      someId,
      reinterpret_cast<const char*>(&newSco.data[0]),
      newSco.data.size()
   );
}

There. Gorgeous.

As a final note, since the API takes a char for the final parameter, I'd consider putting an upper bound on the container size. You can run the function in a loop, sending either CHAR_MAX or newSco.data.size() bytes at a time (whichever is smaller). Otherwise, if you expect more than CHAR_MAX elements in the container, you're going to get a nasty overflow!

Lightness Races in Orbit
  • 358,771
  • 68
  • 593
  • 989
3

This should work:

send(someId, static_cast<char*>(&newSco.data[0]), static_cast<char>(newSco.data.size()));

Additional Info:

How to convert vector to array

What is the difference between static_cast<> and C style casting?

FloIsAwsm
  • 156
  • 1
  • 5
  • @LightnessRacesinOrbit Because of the send function which looks like this `void send(unsigned& id, char* data, char len);`. You will have to call send multiple times anyway when the vector has more than 255 elements. At least this way it is clear from the call! – FloIsAwsm Jun 04 '19 at 11:21
  • @FloIsAwsm not that it matters, but 127 if its 8-bit signed chars. – WhozCraig Jun 04 '19 at 11:22
2

Yes it is reasonable to cast the pointer to char* in this case. In general, you may not refer to the same memory location with different typed pointers due to strict aliasing rules. However, 'char' is a special case so the cast is allowed.

You shouldn't cast uint32_t* to int32_t though, for example, even though it may work in some cases.

Edit: As the comments below noted: casting from uint8_t* to char* may be fine, casting from iterator is not. use .data() instead of .begin().

Richard K. Wade
  • 289
  • 1
  • 5
  • `(char*)&(newSco.data.begin())` is many things; "reasonable" isn't one of them. – WhozCraig Jun 04 '19 at 10:59
  • @WhozCraig Why's that – Lightness Races in Orbit Jun 04 '19 at 11:00
  • @LightnessRacesinOrbit I'd *almost* buy into the cast from iterator; the cast from address-of-temporary-iterator seems a bit far fetched. It is, 4:00am here, so maybe I'm just sleep deprived. – WhozCraig Jun 04 '19 at 11:01
  • @WhozCraig Oh yeah, you're right; that's nonsense :D `&*` it would have to be but now it's ugly. Plus UB if the container is empty (whereas the `data()` and `&x[0]` solutions are not) – Lightness Races in Orbit Jun 04 '19 at 11:06
  • @LightnessRacesinOrbit yeah, the same UB transpires anyway with all the collective answers using `&data[0]`. If that vector is empty that's a dumpster fire waiting to ignite. I also still can't get past that `char len` argument. Someone had better be checking that vector size to be at-or-under `CHAR_MAX` or that too is a sob-story in-wait (unsigned size to likely-shorter signed `char`; ouch). – WhozCraig Jun 04 '19 at 11:09
2

If you can use at least C++11, the std::vector class provides data() which returns the raw array.
If you are before C++11, I'm afraid you have to iterate over the vector and manually build your char*.

But, you cannot static_cast an unsigned char* to a char*. It is not allowed.
But you are lucky, char* is an exception that does not break the strict aliasing rule. So you can use reinterpret_cast instead.

So you may solve your problem as follows:

Before C++11:

std::vector<uint8_t> a; // For the example
a.push_back('a');
a.push_back('b');
a.push_back('c');
a.push_back('d');

char b[a.size()];
for(unsigned int i = 0; i < a.size(); ++i)
{
    b[i] = static_cast<char>(a[i]);
}

After C++11:

std::vector<uint8_t> a {'a', 'b', 'c', 'd'}; //uint8_t aka unsigned char
char * b = reinterpret_cast<char*>(a.data()); // b is a char* so the reinterpret_cast is safe

I hope it can help.

Fareanor
  • 3,773
  • 1
  • 4
  • 26
1

try to use reinterpret_cast. Example:

#include <iostream>
#include <unistd.h>
#include <vector>

int main(int argc, char const *argv[])
{
    std::vector<uint8_t> v{65,66,67,68,69,70};
    char* ptr = reinterpret_cast<char*>(v.data());

    for (auto i{0}; i < v.size(); i++) {
        std::cout << *ptr++ << std::endl;
    }
    return 0;
}

for your case:

void send(someId, reinterpret_cast<char*>(sco.data.data()), sco.data.size());