Reputation: 2127
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 constexpr
s that describe each parameter.
Upvotes: 0
Views: 583
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;
Map
Another similar way would be to map the enum
to the class
es (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 class
es
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();
}
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;
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