Matteo
Matteo

Reputation: 8132

Polymorphic Enums

Polymorphic Enums?

In C++, we often use polymorphism to allow old code to handle new code--for instance, as long as we subclass the interface expected by a function, we can pass in the new class and expect it to work correctly with the code that was written before the new class ever existed. Unfortunately, with enums, you can't really do this, even though there are occasional times you'd like to. (For instance, if you were managing the settings for your program and you stored all of them as enum values, then it might be nice to have an enum, settings_t, from which all of your other enums inherited so that you could store every new enum in the settings list. Note that since the list contains values of different types, you can't use templates.)

If you need this kind of behavior, you're forced to store the enums as integers and then retrieve them using typecasts to assign the particular value to the setting of interest. And you won't even get the benefit of dynamic_cast to help you ensure that the cast is safe--you'll have to rely on the fact that incorrect values cannot be stored in the list.

I'm quoting from a C++ programming tutorial.

Can anybody please explain more deeply and with some examples how Polymorphic Enums work? And in the case I have templates?

Upvotes: 2

Views: 2449

Answers (1)

Chad
Chad

Reputation: 19032

Simply stated, an enum is simply a named constant value, for instance:

enum Settings
{
   setting_number_0,
   setting_number_1,
   setting_number_2,      
};

In the above example, setting_number_X is simply a named constant for the value X, as enumeration values start at 0 and increase monotonically.

Keeping these then, in some type of container gives a basic storage type of integers, but can still be somewhat typesafe.

std::vector<Setting> app_settings;

// this works
app_settings.push_back(setting_number_0);

// this is a compile time failure, even though the underlying storage
// type for Setting is an integral value.  This keeps you from adding
// invalid settings types to your container (like 13 here)
app_settings.push_back(13);

// but you also cannot (directly) add valid setting values (like 1)
// as an integral, this is also a compile time failure.
app_settings.push_back(1);

Now, suppose you wanted to add additional specific setting types and keep them all in a container.

enum DisplaySettings
{
   // ...
};

enum EngineSettings
{
   // ...
};

Now, if you wanted to keep all the settings in a single container, you cannot safely. You could store all the integral values in a container of std::vector<int> or similar, but that breaks down in that you cannot determine what integral types belong to what setting enumerations. Also, since the types are different you cannot store them in a single type-safe container.

The correct way to go about this is would be to store the functionality of the setting in the container, something like this:

#include <vector>
#include <iostream>

// This is our "base class" type so we can store lots of 
// different setting types in our container
class setting_action
{
public:
   // we enable the setting by calling our function
   void enable_setting()
   {
      setting_function_(this);
   }

protected:
   // This is a function pointer, and we're using it to get some
   // compile time polymorphism
   typedef void (*setting_function_type)(setting_action* setting);

   // these can only be constructed by derived types, and the derived
   // type will provide the polymorhpic behavior by means of the 
   // above function pointer and based on the derived type's handler
   setting_action(setting_function_type func)
      : setting_function_(func)
   {
   }

public:
   ~setting_action()
   {
   }

private:
   setting_function_type setting_function_;
};

// This is the derived type, and where most of the magic
// happens.  This is templated on our actual setting type
// that we define below    
template <class Setting>
class templated_setting_action
   : public setting_action
{
public:
   templated_setting_action(Setting setting)
      : setting_action(&templated_setting_action::enable_setting)
      , setting_(setting)
   {
   }

   // This function catches the "enable_setting" call from
   // our base class, and directs it to the handler functor
   // object that we've defined
   static void enable_setting(setting_action* base)
   {
      templated_setting_action<Setting>* local_this = 
         static_cast<templated_setting_action<Setting>*>(base);

      local_this->setting_();
   }

private:
   Setting setting_;
};

// this is just a shorthand way of creating the specialized types
template <class T>
setting_action* create_specialized_setting_action(T type)
{
   return
      new templated_setting_action<T>(type);
}

// Our actual settings:
// this one displays the user name    
struct display_user_name
{
   void operator()()
   {
      std::cout << "Chad.\n";
   }
};

// this one displays a short welcome message    
struct display_welcome_message
{
   void operator()()
   {
      std::cout << "Ahh, the magic of templates.  Welcome!\n";
   }
};

// now, we can have one container for ALL our application settings

std::vector<setting_action*> app_settings;

int main()
{
   // now we can add our settings to the container...
   app_settings.push_back(create_specialized_setting_action(display_user_name()));
   app_settings.push_back(create_specialized_setting_action(display_welcome_message()));

   // and individually enable them
   app_settings[0]->enable_setting();
   app_settings[1]->enable_setting();

   // also, need to delete each setting to avoid leaking the memory
   // left as an exercise for the reader :)
   return 0;
}

Upvotes: 3

Related Questions