Poperton
Poperton

Reputation: 2127

Associate each enum member to a class and get generic info from that class

Suppose I have lots of audio effects into an enum:

enum AudioEffect {
    CHORUS,
    FILTER,
    GAIN,
    OSCILATOR,
    PHASER
};

and for each one, there's a class that specifies how the audio bytes will be processed:

class ChorusProcessor  : public ProcessorBase
{
public:
    ChorusProcessor()
    {
    }
}

class FilterProcessor  : public ProcessorBase
{
public:
    FilterProcessor()
    {
    }
}

Each of these processor classes will have parameters that can be changed, these are the audio controllers (mute/unmute, sliders, etc).

Without making an instance of the processors, is it possible to traverse the enum, and for each enum, get the list of controllable values with its min and max values?

I think C++ is smart enough to let us do these things. I'd begin thinking on how to associate Chorus with ChorusProcessor, Filter with FilterProcessor etc. How would I do that?

Then I'd make all the processor classes derive from a class like AudioEffectDescription which has some constexprs that describe each parameter.

Upvotes: 0

Views: 583

Answers (1)

2b-t
2b-t

Reputation: 2564

You can do that with a bit of trickery but I am not sure how useful it really is. The main problem you will have to face it that generally you can't iterate easily through all values of an enum. As the underlying data type for a normal enum is integer starting you might use a simple loop for a plain enum but this fails for any enum where values are set manually such as

enum class AudioEffect {
  Chorus = 3,
  Filter = 6,
  Gain = 5,
  Oscillator = 2,
  Phaser = 1
};

Generally you will have to create an additional data structure like a std::tuple or a std::vector<AudioEffect>, add all the possible values and loop over using something like a variadic template (As soon as you use template parameters a normal loop won't suffice anymore). Furthermore in C++ there is no such thing as virtual static functions.


Specialised template class

The best I can come up with is:

  • Declare a class template <AudioEffect E> class Processor and specialise it for all possible template parameters. I added two static constexpr functions for getting the min and the max. These will have to be declared in all specialisations. I have used two functions min and max that return a minimum and a maximum value but you could return a std::string_view (or in C++20 you will be able to return a constexpr std::string) that contains more information about the different limits.

    template <AudioEffect E>
    class Processor: ProcessorBase {
    };
    
    template <>
    class Processor<AudioEffect::Chorus>: ProcessorBase {
      public:
        // Or a single function for printing all the settings of this class
        static constexpr int min() {
          return 1;
        }
        static constexpr int max() {
          return 99;
        }
    };
    
  • Then I can print all the limits with a variadic template function and C++17 fold expressions (assuming here that each instance of the template class Preprocessor has a static constexpr function min and another one max

    template <AudioEffect... E>
    std::string print() {
      std::stringstream ss;
      ((ss << E << "\t min: " << Processor<E>::min() << ",\t max: " << Processor<E>::max() << std::endl), ...);
      return ss.str();
    }
    
  • This print function can be called inside the main listing all possible parameters

    std::cout << print<AudioEffect::Chorus,AudioEffect::Filter,AudioEffect::Gain,AudioEffect::Oscillator,AudioEffect::Phaser>() << std::endl;
    

Try it here!


Map

Another similar way would be to map the enum to the classes (that do not have to be specialised templates anymore) with traits like here by

  • Defining your classes like

    class ChorusProcessor: ProcessorBase {
      public:
        static constexpr int min() {
          return 1;
        }
        static constexpr int max() {
          return 99;
        }
    };
    
  • Then specialising a template struct Map that maps in between the enum values and the classes

    template<AudioEffect> struct Map;
    
    template<> struct Map<AudioEffect::Chorus> {
      using type = ChorusProcessor;
    };
    
  • And introducting a print function again with variadic templates like

    template <AudioEffect... E>
    std::string print() {
      std::stringstream ss;
      ((ss << E << "\t min: " << Map<E>::type::min() << ",\t max: " << Map<E>::type::max() << std::endl), ...);
      return ss.str();
    }
    

Try it here!

This is pretty similar to the solution proposed in the comments that makes use of an std::tuple

using Processors = std::tuple<
  ChorusProcessor,
  FilterProcessor,
  GainProcessor,
  OscilatorProcessor,
  PhaserProcessor
>;

std::tuple_element_t<AudioEffect::Filter, Processors>::value

Brief but dirty

If you use an enum without specifying values explicitly you could also go away with a dirty solution only supplying the first and the last value of the enum (C++17)

template <AudioEffect Begin, AudioEffect End>
std::string print() {
  std::stringstream ss;
  ss << Begin << "\t min: " << Processor<Begin>::min() << ",\t max: " << Processor<Begin>::max() << std::endl;
  if constexpr (Begin != End) {
    ss << print<static_cast<AudioEffect>(static_cast<int>(Begin)+1), End>();
  }
  return ss.str();
}

This way you could simply call it with the first and the last value of the enum

std::cout << print<AudioEffect::Chorus,AudioEffect::Phaser>() << std::endl;

Try it here!


Enum reflection

Finally with the GCC, Clang and MSVC compiler there is a hacky way to get some enum properties with the __PRETTY_FUNCTION__ identifier which is described here. (There are a few typos in the original code and it is not constexpr so better try it here.) With it you could completely get rid of the Begin and End by looping from 0 to the detected number of enum values. This is a bit more complicated so I will just leave the code here for you to try. With it you can output all classes without specifying all the enum values explicitly:

std::cout << print<AudioEffect>() << std::endl;

Upvotes: 1

Related Questions