3

EDIT: thanks for responses. Adding breaks to the switch; the problem is indeed that I want to be able to use the struct after the switch statement.

I want to be able to do something like the following:

int foo (int type, void* inVar) {    
    switch(type) {
    case 0:
        struct struct-type-one mystruct = *( (struct struct-type-one*) inVar);
        break;
    case 1:
        struct struct-type-two mystruct = *( (struct struct-type-two*) inVar);
        break;
    case 2:
        struct struct-type-three mystruct = *( (struct struct-type-three*) inVar);
        break;
    }
    // use mystruct.data
}

Basically, the idea is that inVar is a pointer to a struct, and type tells us what type we need. The structs all have data, but the type of the struct determines the type of the data. I want to do this because in the code after the switch, we can then do operations on mystruct.data without having to worry about what type the data actually is.

My question is, how can we do this? This would be easy in Python, for example, but C++ doesn't like how mystruct is "cross initialized", i.e. can have different types depending on which branch of the switch statement is used. Templating was an idea that we had wrestled with, but it's difficult because the names of the struct are different - e.g. struct intStruct, struct floatStruct. This is important because the size of these structs depends on the data type. Any ideas?

A.Wan
  • 1,818
  • 3
  • 21
  • 34
  • 2
    you should use `break;` after each case. Also, don't use reinterpret case in C++ unless it's necessary. In this case, just use `dynamic_cast`. – segfault Apr 03 '14 at 15:36
  • 3
    Bertrand Meyer chose to let the Eiffel language not have `enum` types, in large part in order to not place the temptation to do the above silly type switching (it's a known **anti-pattern**) in front of programmers. Instead, to do polymorphism, use polymoporphism. C++ supports both static and dynamic polymorphism, and I suspect the latter, using virtual member functions, is what you need. – Cheers and hth. - Alf Apr 03 '14 at 15:39
  • 3
    Another extreme problem with that code is that you're using `void*`, which means that you have **discarded type information**. That's not smart: the antipattern code is all about "undoing" your earlier needless discard. Just *don't* do the discard -- use types. – Cheers and hth. - Alf Apr 03 '14 at 15:41
  • 3
    Oh, googling it I found a sort of tutorial ready made: http://sourcemaking.com/refactoring/replace-conditional-with-polymorphism – Cheers and hth. - Alf Apr 03 '14 at 15:43
  • Just to emphasise: while you can do this in Python, it’s *also* an anti-pattern in Python. Don’t write such code. – Konrad Rudolph Apr 03 '14 at 15:44
  • @segfault You can't `dynamic_cast` from a `void*`. – James Kanze Apr 03 '14 at 16:45
  • @KonradRudolph It's actually fairly frequent in Python. But of course, you don't need the switch; variables in Python don't have a type; only the objects they point to have a type. – James Kanze Apr 03 '14 at 16:46
  • @James Yes but frequent ≠ good. – Konrad Rudolph Apr 03 '14 at 17:03
  • @KonradRudolph That's how you implement polymorphism in Python. You don't have a base class with abstract functions; you just call the function you want, and expect the object you've been given to implement it. – James Kanze Apr 04 '14 at 08:09
  • @James Uh. That’s how you *can* implement polymorphism in Python. You don’t have to. And it’s not a particular good implementation. To clarify: I’m talking about the `type` variable toggle, not about the fact that you work with objects of different types without common base class (that’s just ad hoc polymorphism or duck typing and it’s totally fine). – Konrad Rudolph Apr 04 '14 at 08:53
  • @KonradRudolph I've never seen the type variable toggle in Python; duck typing means that it isn't necessary. What is common in Python is to pass totally different types, unrelated to one another by inheritance, to a function, which is what he seems to be trying to do. – James Kanze Apr 04 '14 at 09:07

4 Answers4

4

I don't believe it's possible to do what you want directly (and even if it was, it is an anti pattern in C/C++). However, if your want to use mystruct.data (and not mystruct itself), then you can rewrite your code this way:

int foo (int type, void* inVar) {    
    void* mystruct_data = NULL;

    switch(type) {
    case 0:
        mystruct_data = ((struct struct-type-one*) inVar)->data;
        break;
    case 1:
        mystruct_data = ((struct struct-type-two*) inVar)->data;
        break;
    case 2:
        mystruct_data = ((struct struct-type-three*)inVar)->data;
        break;
    }

    // use mystruct_data (and don't forget to check it's not NULL)
}

Note that you need to declare variable before – whatever you declare inside switch() is scoped to within that switch() only.

Alternatively, if your data processing depends on the type, they you can do something like this:

int foo (int type, void* inVar) {    
    switch(type) {
    case 0:
        data_process_1((struct struct-type-one*) inVar);
        break;
    case 1:
        data_process_2((struct struct-type-two*) inVar);
        break;
    case 2:
        data_process_3((struct struct-type-three*) inVar);
        break;
    }
}
Andrey
  • 1,561
  • 9
  • 12
  • I think the 2nd code will be enough for such a problem. – acegs Apr 03 '14 at 16:51
  • The second part of the code looks similar to what I'm thinking of. However I was trying to avoid having to write three different data_process functions since they will all do the same thing, just with different types. This would definitely work though! – A.Wan Apr 03 '14 at 17:24
3

If (as the tag suggests) you are writing c++, you should be using polymorphism or templates or some other language feature that simplifies such things.

Now what you're asking is often seen in legacy C code so for this language, it's common to create variant types using unions, and this is how you do it (the following is ofcourse also valid c++):

Lets suppose you have 3 types as in the question

struct Type1 {/*some implementation*/};
struct Type2 {/*some implementation*/};
struct Type3 {/*some implementation*/};

Then you would define a variant type as so

struct TVariant
{
    int mtype; 
    union {
        struct Type1 *m1;
        struct Type2 *m2;
        struct Type3 *m3;
    };
};

and make use of it in this fashion

int foo (int type, void* inVar) 
{ 
    struct TVariant v;
    switch (type) 
    {
    case TYPE_ONE:
        v.mtype = 1;
        v.m1    = (struct Type1*)inVar;
        break;
    case TYPE_TWO:
        v.mtype = 2;
        v.m2    = (struct Type2*)inVar;
        // .... and so on
    // .....
    }
    // Use your v, actually the correct member of the union in v
}

Some remarks :

  • If it's just for one function then the member mtype of TVariant is not needed, you can keep track of the type just by using the function argument int type
  • TYPE_ONE, TYPE_TWO etc are integer constants that map to the types Type1, Type2 etc
  • It's both more cumbersome and slower (due to information discarding and casts) than using templates
  • As a simpler solution (for c++) using overloading would do the trick, as every different version of foo would have different implementations for each Type
  • There are multiple variations on how to create variant objects that make use of unions' properties.
  • Unions where used this way to achieve polymorphism in C. In C++ you have that alredy so none of these tricks is needed.
  • Last and most important, the main drawback is that the union will be sized as much as the bigest of its member types so if (as implied in the question) you only want pointers to your types, then m1, m2, m3 should be pointers and the union's size would always have the size of a single pointer.
Community
  • 1
  • 1
Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153
1

You're almost there:

int foo (int type, void* inVar) {    
    switch(type) {
    case 0: {
        struct_type_1& mystruct = *static_cast<struct_type_1*>(inVar);
        // use mystruct here
    }
    break;
    case 1: {
        // same thing again
    }
    }
}

Your biggest problem was probably not using braces around each case body. That meant your mystruct variables were colliding.

A key phrase you may want to look up to read more about this sort of thing is "discriminated union." Also look at Boost Variant for another way to handle it.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
0

You may use something like:

int bar(Data& data);

int foo (int type, void* inVar) {    
    switch(type) {
        case 0: return bar(static_cast<struct_type_0*>(inVar)->data);
        case 1: return bar(static_cast<struct_type_1*>(inVar)->data);
        case 2: return bar(static_cast<struct_type_2*>(inVar)->data);
    }
    return -1;
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302