Defining struct elements according to an enum value of that same struct.

Hello. I'm trying to code a card game of sorts, inspired in MTG and YGO. I've declared an enum of possible card types and a struct, which contains as an element its type. The problem arises when I need to define the elements of said struct given the type, for instance, if (card.type == creature), I'll need a short unsigned int strength and a short unsigned int resistance, but that wouldn't be the case if (card.type == magic). What would be a good way of doing this? I'd not like to create a struct for every card type and it would be preferable to have all types in one same struct. Thank you.
This is a case where object oriented programming would help.

If card type, such as creature, magic, ..., would be associated with a set of things they can do (methods), you can define categories of objects.

Why would you do this when you can represent everything as an enum? So the language can help you with your ideas, rather than you having to code all your ideas using integer flags. You end up with simpler and usually more reliable code that's easier to extend.

If you're you're interested in pursuing this method, then we'll need a bit more detail on what you're doing.
Last edited on
Kbw is correct, you are effectively writing in C which has only the crudest object support and forces you to do this by hand.

You can do it, of course.
one easy way is to add all the crap you need to the struct, so it has fields it does not need and you know which set of fields to use based off the type. It wastes a little space and is clunky, but it will get it done.

that is a little crude: a union would clean up the mess and stop wasting MOST of the memory.
looks like
1
2
3
4
5
6
7
8
9
10
11
12
13
union data //this can be a union of simple structs...
{
   crittercard cc;
   magiccard mc;
}
struct card
{
    myenum type;
    data       mydata; 
}

if(somecard.type == magic)
somecard.mydata.mc.whatever = hoopajoo;


but again this is fairly barbaric in c++. Barbarism has its moments, though. If you add a char array of the size of the union into the union (remember to keep this updated if you tamper with the underlying structs!!!) you can read and write that in binary as a 'card' to a file etc -- it serializes all you need automatically if you don't have any pointers or c++ containers (includes string) or anything nontrivial in the structs. It may be wise to pull your card decks from a file, so you can grow the game by adding cards without recompiling or anything.
Last edited on
In C++ there are polymorphic classes and since C++17 std::variant type and std::visit etc

https://en.cppreference.com/w/cpp/utility/variant
ttps://en.cppreference.com/w/cpp/utility/variant/visit
https://www.learncpp.com/cpp-tutorial/virtual-functions/

You might find this article of interest https://www.fluentcpp.com/2022/02/09/design-patterns-vs-design-principles-visitor/ (and others in the design series)
Use a discriminated union. Building off @jonnin's example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <cstdio>
#include <cassert>

struct creature_card { void sound_off() { std::puts("a thunderous cry echoes"); } };
struct magic_card { void cast() { std::puts("electricity arcs from your fingertips"); } };

enum class card_type { creature, magic, }; 

class card
{
  card_type type;  
  
  union 
  {
    creature_card creature;
    magic_card magic;
  };
  
public:
  ~card() = default;
  
  card(card const&) = default;
  card& operator=(card const&) = default;
  card(card&&) noexcept = default;
  card& operator=(card&&) noexcept = default; 
  
  /* explicit */ card(creature_card cc): type {card_type::creature}, creature{cc} {}
  /* explicit */ card(magic_card mc): type {card_type::magic}, magic{mc} {}
  
  card_type get_type() const noexcept { return type; }; 
  
  magic_card& as_magic_card() noexcept 
  { 
    assert(get_type() == card_type::magic);
    return magic;
  }
  
  creature_card& as_creature_card() noexcept 
  {
    assert(get_type() == card_type::creature);
    return creature;
  }
};

int main()
{
  
  card a { creature_card{} };
  card b { magic_card{} };
  
  if (a.get_type() == card_type::creature)
    a.as_creature_card().sound_off();
    
  if (b.get_type() == card_type::magic)
    b.as_magic_card().cast();
}


This program implements the core idea behind std::variant. It may be worthwhile to study that once the idea is understood.
Last edited on
Regarding a discriminated union....

Would you, mbozzi, recommend using std::variant since this is C++ code?

cppreference does mention it as a "type-safe discriminated union." (see the bottom of this page):

https://en.cppreference.com/w/cpp/utility/optional

I have to admit the notion of union vs. discriminated union is more than a bit confusing to me.
std::variant represents a type-safe union. There is the inbuilt c/c++ union which has been 'extended' to 'sort of work' with union of C++ structs/classes etc. However there are big potential issues in doing this. That's why std::variant was introduced to 'fix' the problems using union.

Basically unless you use POD (or a simple struct composed of POD without constructor/destructor/virtual etc ) then don't use union - use std::variant.
Last edited on
I understand the difference between a regular union and std::variant, as well as the concept of being type safe.

What I am confused is with the terminology, "discriminated." If that boils down to "this means 'type safe'" then m'ok.

Doing a 'net search for "discriminated union" really doesn't find anything that explains it without being a load of high-falutin' gobbley-gook techno-babble to me.

Well, after flailing around, a LOT, I found this Dr. Dobb article:

http://erdani.org/publications/cuj-04-2002.php.html

The mists of "huh?" begin to part. Still rather hazy, so I'll save the link for further perusal and study.
Heh, the 3 part article's published date is April 1, 2002. If'n I didn't know any better I'd think I was smelling a rather pungent April Fool's joke.
What I am confused is with the terminology, "discriminated."
What distinguishes a "non-discriminated" union from a "discriminated" union is the presence of a variable that says what the union represents. This variable is called the discriminator. Unsurprisingly.

In the above code the discriminator is declared on line 11:
9
10
11
12
13
14
15
16
17
18
19
20
class card
{
  card_type type; // <-- discriminator
  //   says whether *this represents a magic card or a creature card
  
  union 
  {
    creature_card creature;
    magic_card magic;
  };
  // ...
};

Inside std::variant the discriminator is the value you get back from std::variant<T0, T1, T2>::index. It gives you 0 if it stores a T0, 1 if it stores a T1, and so on.

Would you, mbozzi, recommend using std::variant since this is C++ code
Yes. Although I think the above is reasonable, variant leads to the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <cstdio>
#include <variant>

struct creature_card { void sound_off() { std::puts("a thunderous cry echoes"); } };
struct magic_card { void cast() { std::puts("electricity arcs from your fingertips"); } };

int main() 
{
  std::variant<creature_card, magic_card> a{creature_card{}}, b{magic_card{}};
  
  constexpr struct
  { 
    void operator()(creature_card cc) const { cc.sound_off(); }
    void operator()(magic_card mc)    const { mc.cast(); } 
  } do_thing;

  std::visit(do_thing, a);
  std::visit(do_thing, b);
}

Last edited on
Topic archived. No new replies allowed.