1

I want to create a basic parsing method which takes a vector <uint8_> as an input using C++. Based on the actual values of the individual bytes this method should return a struct representing this data.

For example:

Input 1: {0x10, 0x02, 0x03}
Input 2 {0x20, 0x05, 0x02}

The first byte should represent the type of the object with 0x10 = cube and 0x20 = sphere.
The second byte is depending on the type either width of the cube or diameter of the sphere.
The third byte is either the volume of the cube or the mass of the sphere.

Can I create a method which takes the input vector and returns based on the values inside this vector one of these two different structs:

struct cube
{
    int width;
    int volume;
};

struct sphere
{
    int diameter;
    int mass;
};
p0fi
  • 1,056
  • 12
  • 28
  • 4 answers already. I wonder why "union" has not yet been mentioned. Have I missed anything which forbids the use of unions? Or to ask differently: Would you (OP) be interested in a solution using a union type? Answerers, what kept you from using a union? – Yunnosch Apr 03 '17 at 15:22
  • There is nothing holding me back from using a union type except that I do not know how to use them ;) If there is an elegant solution using unions, why not? – p0fi Apr 03 '17 at 15:56
  • Do you require the parse result in the return type? I used an out-parameter via pointer (C++ way would be reference.) If needed, I change to returning a union-struct. In my job (small embedded systems) I am expected to handle struct parameters and return values via pointer, that's why. But I can adapt to your needs. – Yunnosch Apr 03 '17 at 17:19
  • If unions are an option, would you like to add a corresponding tag? (Even if you do not like my answer.) – Yunnosch Apr 03 '17 at 17:21
  • @Yunnosch I'm not entirely sure actually. It's also supposed to run on a somewhat embedded system but the return type solution was looking more straight forward to me. I don't actually know if there are any definitive benefits or drawbacks to each method. – p0fi Apr 03 '17 at 17:26
  • No problem. I changed to return value. – Yunnosch Apr 03 '17 at 17:32

4 Answers4

1

In order to return different types from a function, the types must be related (see below). In addition, the return type must be a pointer or a smart pointer.

If you wish to communicate data of unrelated types back to the caller, you have two options:

  • Take reference parameters to struct of each possible kind, or
  • Take a callback that accepts elements of each possible kind.

First approach:

enum shape_t {Cube, Sphere};
shape_t parse(vector<uint8_t> data, cube &c, sphere& s) {
    if (<data represents a cube>) {
        c.width = ...
        c.volume = ...
        return Cube; 
    } else if (<data represents a sphere>) {
        s.diameter = ...
        s.mass = ...
        return Sphere;
    } else {
        ... // Handle error
    }
}

Second approach:

struct parse_callback {
    virtual void cube(const cube& c);
    virtual void sphere(const sphere& s);
};
...
void parse(vector<uint8_t> data, parse_callback& cb) {
    ...
    if (<data represents a cube>) {
        cube c;
        c.width = ...
        c.volume = ...
        cb.cube(c); 
    } else if (<data represents a sphere>) {
        sphere s;
        s.diameter = ...
        s.mass = ...
        cb.sphere(s);
    }
}

If you do not mind making your classes inherit from a common base, you can return a smart pointer to a polymorphic type:

enum shape_kind {Cube, Sphere};

struct shape {
    virtual ~shape() {}
    virtual shape_kind kind() = 0;
};

struct cube : public shape {
    shape_kind kind() { return Cube; }
};
struct sphere : public shape {
    shape_kind kind() { return Sphere; }
};

shared_ptr<shape> parse(const vector<uint8_t> data) {
    if (<data represents a cube>) {
        return shared_ptr<shape>(new cube);
    } else {
        return shared_ptr<shape>(new sphere);
    }
}

Demo.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
0

void find_struct(vector input, cube &c1, sphere &sp1) {

if ( input[0] == 0x10 )
{
    c1.width = input[1];
    c1.volume = input[2];
}
else if (input[0] == 0x20)
{
    sp1.diameter = input[1];
    sp1.mass = input[2];
}

}

int main() {

vector <unsigned int> input1 = { 0x10, 0x02, 0x03 };
vector <unsigned int> input2 = { 0x20, 0x05, 0x02 };

cube c1;
c1.volume = 0;
c1.width = 0;

sphere s1;
s1.diameter = 0;
s1.mass = 0;

find_struct(input1,c1,s1);

if (c1.volume != 0)
{
    std::cout << "cube is returned" << std::endl;
}
if (s1.diameter != 0)
{
    std::cout << "sphere is returned" << std::endl;
}

}

As an alternative, assign struct member 0 as default.

Then send both cube and sphere instance to the function find_struct, This function returns the modified c1 ans sp1

Check if struct value is not zero !!!!

Ibrahim
  • 320
  • 2
  • 7
  • Thanks but this solution is not really what I'm looking for since this doesn't really work with return types but reference parameters. This will get out of hand with a growing number of different shapes. – p0fi Apr 03 '17 at 16:54
0

Only to keep it simple and still correct I think it is better to keep the approach suggested by m.antkowicz and return a pointer to a base object inorder to provide a polymorphic behavior:

struct shape {
    //...
};

struct cube: shape { };
struct sphere: shape { };

// ---

shape* foo(vector<uint8_> v) {
    //...
}

In this way your method will be able to return either a sphere or a cube, or even add new shapes easily in the future with no breaking changes.

Of course that instead of returning a simple pointer to shape (shape*) it could be a smart pointer as std::shared_ptr<shape> but for now I find important to keep the answer simple and focused on the actual solution.

meJustAndrew
  • 6,011
  • 8
  • 50
  • 76
0

Here is a proposal based on union and with a strong C-flavor, i.e. little object orientation.
(OP invited a proposal with unions, I guess the often quoted risks of unions are not a problem for the sake of this discussion.)
The main design descion is the use of a union.

UPDATE: Change to API with return value.

I added demonstrator code, showing the possibility of using information available inside the variable for error handling and for detecting the kind of shape.
I believe that any client code (which handles the result of the parser) needs to find out which kind of shape is inside. It is not enough to design an API which can use a single datatype.
I speculate (not blaming OP for lack of info) that if different parts of client code could be called, each of which implicitly being aware of the kind of shapes it receives,
then there would not be a need to find a data design to carry all shapes in one type.

typedef enum tenCubeOrSphere_tag
{
    nenInvalid   = 0x0,
    nenCube      = 0x10,
    nenSphere    = 0x20
} tenCubeOrSphere;

typedef struct tstCube_tag
{
    tenCubeOrSphere enWhich;
    // Note that this is the same for cube and for sphere

    int width;
    int volume;
} tstCube;

typedef struct tstSphere_tag
{
    tenCubeOrSphere enWhich;
    // Note that this is the same for cube and for sphere

    int diameter;
    int mass;
} tstSphere;

typedef union tunShape_tag
{
    tstCube   Cube;
    tstSphere Sphere;
    tstCube   Unknown;
    /* just for selfexplaining client code,
       could also be tstSphere, same thing */
} tunShape;

// That's it, below is just demonstrator code.

tunShape parse(/* assume here an input parameter,
               a reference to parseable vector */)
{   tunShape Out;

    { /* parsing happens here,
         assume it finds a cube */
         Out.Cube.enWhich=nenCube;
         Out.Cube.volume = /* let's say ... */   5;
         Out.Cube.width  = /* hmm...        */ 125;

      /* in case of sphere */
         Out.Sphere.enWhich =nenSphere;
         Out.Sphere.diameter= /* let's say ... */ 30;
         Out.Sphere.mass    = /* hmm...        */ 14000;
    }

    return Out;
}

void client (void)
{   tunShape unReceiver;
    unReceiver = parse(/* iput vector */);
    if        (unReceiver.Unknown.enWhich == nenInvalid)
    {/* error handling */
    } else if (unReceiver.Unknown.enWhich == nenCube)
    {   std::cout << "It is a cube: "   << unReceiver.Cube.volume << "," << unReceiver.Cube.width << std::endl;
    } else /* obviously a sphere */
    {   std::cout << "It is a sphere: " << unReceiver.Sphere.mass << "," << unReceiver.Sphere.diameter << std::endl;
    }
}
Yunnosch
  • 26,130
  • 9
  • 42
  • 54
  • I tested on windows mingw, it just needs suitable includes and a call `client();` from main. – Yunnosch Apr 03 '17 at 17:11
  • I understand OPs comment to indicate he knows unions theoretically. I checked, stackoverflow has no documentation on unions. (Happy about links if I am wrong.) If some explanation is needed let me know. – Yunnosch Apr 03 '17 at 17:14