Playing with templates and type traits

The Partridge Family were neither partridges nor a family. Discuss.
albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Playing with templates and type traits

Post by albinopapa » October 8th, 2018, 8:58 pm

One thing I'm noticing with std::variant is the maintenance is very similar to using enums and switch/case.

switch/case with enum Example:

Code: Select all

struct Dog{ void move(); };
struct Fish{ void move(); };
struct Bird{ void move(); };

enum class Animals{ DOG,BIRD,FISH };

struct Animal
{
     Animals type = DOG;
};

class AnimalRepo
{
public:
     Dog& get_dog(){ return dog; }
     Bird& get_bird(){ return bird; }
     Fish& get_fish(){ return fish; }
};
void Movable::Do( const Animal& _animal )
{
     switch( _animal.type )
     {
          case Animals::DOG:
               animal_repo.get_dog().move();
               break;
          case Animals::BIRD:
               animal_repo.get_bird().move();
               break;
          case Animals::FISH:
               animal_repo.get_fish().move();
               break;
     }
}
variant example:

Code: Select all

struct Dog{};
struct Fish{};
struct Bird{};
struct Animal
{
     std::variant<Dog,Fish,Bird> type = Dog();
};

struct MoveVisitor
{
     void operator()( Dog& _animal ){ _animal.move(); }
     void operator()( Fish& _animal ){ _animal.move(); }
     void operator()( Bird& _animal ){ _animal.move(); }
};
void Movable::Do( Animal& _animal )
{
     std::visit( MoveVisitor, _animal_type );
}
Now, let's add a goat.

switch/case with enum Example:

Code: Select all

struct Dog{ void move(); };
struct Fish{ void move(); };
struct Bird{ void move(); };
struct Goat{ void move(); };

enum class Animals{ DOG,BIRD,FISH,GOAT };

struct Animal
{
     Animals type = DOG;
};

class AnimalRepo
{
public:
     Dog& get_dog(){ return dog; }
     Bird& get_bird(){ return bird; }
     Fish& get_fish(){ return fish; }
     Goat& get_goat(){ return goat; }
};
void Movable::Do( const Animal& _animal )
{
     switch( _animal.type )
     {
          case Animals::DOG:
               animal_repo.get_dog().move();
               break;
          case Animals::BIRD:
               animal_repo.get_bird().move();
               break;
          case Animals::FISH:
               animal_repo.get_fish().move();
               break;
          case Animals::GOAT:
               animal_repo.get_goat().move();
     }
}
variant example:

Code: Select all

struct Dog{ void move(); };
struct Fish{ void move(); };
struct Bird{ void move(); };
struct Goat{ void move(); };
struct Animal
{
     std::variant<Dog,Fish,Bird,Goat> type = Dog();
};

struct MoveVisitor
{
     void operator()( Dog& _animal ){ _animal.move(); }
     void operator()( Fish& _animal ){ _animal.move(); }
     void operator()( Bird& _animal ){ _animal.move(); }
     void operator()( Goat& _animal ){ _animal.move(); }
};
void Movable::Do( Animal& _animal )
{
     std::visit( MoveVisitor, _animal_type );
}
In the switch/case version, you must maintain a list of animal types, same with variant
In the switch/case version, you must maintain the switch/case cases, with variant you must maintain a list of functions matching the variant types.

The only reason I bring this up is because of an issue I ran into. I had my program compiling and running, then decided to add some system classes, also added some more messages specific to those classes. Well, I was getting errors saying that there was an error with function overloading, I can't recall now what the exact message was. Well, the issue was that I hadn't added the new message handling functions to the older classes. This can both be a relief and discouraging all at the same time. Without VS telling me exactly what is wrong, finding the errors is really difficult. Luckily, the output window had more detailed information than the Errors window.
Example wrote: "missing function overloads for <list of types> in class <name of class(es)>" would have been a nice error message.
On the bright side though, this was a compile time error. In this particular case, had it not been a compile time error, no negative side affects would have occurred because the old classes didn't need to handle the new messages.

My workaround is to just define the functions that handle the messages that the current class cares about, and define an empty template function that handles the rest.

This will allow me to add new messages without having to come back to classes that are already complete. This could possibly be a "shot myself in the foot" moment though if I do add messages that the older classes could or should handle and I forget.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Playing with templates and type traits

Post by albinopapa » October 8th, 2018, 9:15 pm

So, I think what I was trying to get at was while in the examples, there was only one place to maintain a list of functions, in practice you'll probably have several places you'll need to maintain a list of possible visitor functions.

My system classes thus far are; Collidable, Controllable, Movable and Drawable.
This means I need pretty much the same functions for all Entity types and all message types each system class handles. There is a lot of repeated code in the declarations, and some in the definitions.

I'm almost to the point I can start testing the game logic, then I'll start benchmarking. Of course, I'm not too excited to recreate an ECS using the usual inheritance idioms to have something to compare it to.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Post Reply