How can one do this, which is obviously impossible C++, in real C++?:
Type decodeUiEnum(UiEnum myEnum) { // impossible: cannot return a data type
// one switch statement to rule them all
switch(myEnum) {
case USER_SELECTED_GREYSCALE: return GreyscalePixel;
case USER_SELECTED_RGB: return RgbPixel;
...
}
}
void doSomeGraphicsMagic1(UiEnum myEnum) {
...
Foo<decodeUiEnum(myEnum)> a(...); // impossible: type not available at compile
// time
...
}
void doSomeGraphicsMagic2(UiEnum myEnum, int blah) {
...
Bar<int, decodeUiEnum(myEnum)> b(...); // impossible
...
}
and the like, so you can just add new types to the top switch statement and not have to modify the other code below it, so long as that code is suitably generic of course? As otherwise, you would need a switch statement within each function to do the necessary type mapping into the templates, which is not as much maintainable code, and lots of duplication. So more generally - if this is approaching it the wrong way, how do we fulfill that intended property of the code?
That is, what I want to do is, in a function taking an enum as parameter, instantiate a template type where the template parameter depends on the enum, without having a switch-on-enum in every function.
Yes it is actually possible.
Trick is based on partial template specification, this approach used by std::get
For example:
#include <iostream>
// specify an enumeration we will use as type index and related data types
enum class UiEnum {
GRAY_SCALE,
RGB_PIXEL
};
struct GreyscalePixel;
struct RgbPixel;
// make base template class
template<UiEnum _EV>
struct ui_enum_type {
};
// do partial type specification trick
// insert typedefs with data type we need for each enumeration value
template<>
struct ui_enum_type<UiEnum::GRAY_SCALE> {
typedef GreyscalePixel pixel_type;
};
template<>
struct ui_enum_type<UiEnum::RGB_PIXEL> {
typedef RgbPixel pixel_type;
};
// demo classes to demonstrate how trick is working at runtime
template<typename T>
struct demo_class {
};
template <>
struct demo_class<GreyscalePixel> {
demo_class()
{
std::cout << "GreyscalePixel" << std::endl;
}
};
template <>
struct demo_class<RgbPixel> {
demo_class()
{
std::cout << "RgbPixel" << std::endl;
}
};
// use swithc trick
static void swich_trick(std::size_t runtimeValue)
{
switch( static_cast<UiEnum>(runtimeValue) ) {
case UiEnum::GRAY_SCALE: {
demo_class< ui_enum_type<UiEnum::GRAY_SCALE>::pixel_type > demo1;
}
break;
case UiEnum::RGB_PIXEL: {
demo_class< ui_enum_type<UiEnum::RGB_PIXEL>::pixel_type > demo2;
}
break;
}
}
int main(int argc, const char** argv)
{
// Do runtime based on the trick, use enum instead of data type
for(std::size_t i=0; i < 2; i++) {
swich_trick(i);
}
return 0;
}
In any case my suggestion - use classic polymorphism instead of template meta-programming over complication. Most modern compilers doing de-virtualization during optimization. For example:
#include <iostream>
#include <memory>
#include <unordered_map>
enum class UiEnum {
GRAY_SCALE,
RGB_PIXEL
};
class GraphicsMagic {
GraphicsMagic(const GraphicsMagic&) = delete;
GraphicsMagic& operator=(const GraphicsMagic&) = delete;
protected:
GraphicsMagic() = default;
public:
virtual ~GraphicsMagic( ) = default;
virtual void doSome() = 0;
};
class GreyscaleGraphicsMagic final: public GraphicsMagic {
public:
GreyscaleGraphicsMagic():
GraphicsMagic()
{
}
virtual void doSome() override
{
std::cout << "GreyscalePixel" << std::endl;
}
};
class RgbGraphicsMagic final: public GraphicsMagic {
public:
RgbGraphicsMagic():
GraphicsMagic()
{
}
virtual void doSome() override
{
std::cout << "RgbPixel" << std::endl;
}
};
int main(int argc, const char** argv)
{
std::unordered_map< UiEnum, std::shared_ptr< GraphicsMagic > > handlers;
handlers.emplace(UiEnum::GRAY_SCALE, new GreyscaleGraphicsMagic() ) ;
handlers.emplace(UiEnum::RGB_PIXEL, new RgbGraphicsMagic() );
for(std::size_t i=0; i < 2; i++) {
handlers.at( static_cast<UiEnum>(i) )->doSome();
}
return 0;
}
You could use std::variant
, and then have consuming code std::visit
that variant.
First we want a template for "pass a type as a parameter"
template <typename T>
struct tag {
using type = T;
};
Then we define our variant and the factory for it.
using PixelType = std::variant<tag<GreyscalePixel>, tag<RgbPixel>>;
PixelType decodeUiEnum(UiEnum myEnum) {
switch(myEnum) {
case USER_SELECTED_GREYSCALE: return tag<GreyscalePixel>{};
case USER_SELECTED_RGB: return tag<RgbPixel>{};
...
}
}
Now our methods can be written as visitors over PixelType
void doSomeGraphicsMagic1(UiEnum myEnum) {
std::visit([](auto t){
using Pixel = decltype(t)::type;
Foo<Pixel> a(...);
}, decodeUiEnum(myEnum));
}
int doSomeGraphicsMagic2(UiEnum myEnum, int blah) {
return std::visit([blah](auto t){
using Pixel = decltype(t)::type;
Bar<int, Pixel> a(...);
return a.frob();
}, decodeUiEnum(myEnum));
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.