I came up with something that sort of works, but I think some template metaprogramming wizards are going to have to weigh in on whether there's a better way. Here's what I came up with:
First some "model"-ish stuff:
enum class ImageType : uint8_t {
RGB,
CMYK,
Grayscale,
Invalid
};
struct Image {
Image(ImageType type) : img_type(type) {};
ImageType img_type;
// other stuff...
};
Then our templated converter class
template <ImageType T>
struct ImageConverter {
public:
Image ConvertImage(const Image& img);
private:
void some_shared_code(Image& img) {
// do stuff...
};
};
And the corresponding specialized versions of it:
template <> Image ImageConverter<ImageType::RGB>::ConvertImage(const Image& img)
{
Image foo = img;
foo.img_type = ImageType::RGB;
some_shared_code(foo);
return foo;
};
template <> Image ImageConverter<ImageType::CMYK>::ConvertImage(const Image& img)
{
Image foo = img;
foo.img_type = ImageType::CMYK;
some_shared_code(foo);
return foo;
};
template <> Image ImageConverter<ImageType::Grayscale>::ConvertImage(const Image& img)
{
Image foo = img;
some_shared_code(foo);
foo.img_type = ImageType::Grayscale;
return foo;
};
And now the fun part! Essentially I've used a variadic template to recursively search through a list of options provided at the call site.
template<ImageType T, ImageType... Args> struct _maker
{
Image operator()(ImageType desiredType, const Image& inImage)
{
if (T == desiredType)
{
auto converter = ImageConverter<T>();
return converter.ConvertImage(inImage);
}
else
{
return _maker<Args...>()(desiredType, inImage);
}
};
};
Once I had this recursive template thing going, I needed a way to stop at the bottom of the recursion. I'm not thrilled about having to have added Invalid
to the model to have something to stop on, but it was straight forward enough.
template<> struct _maker<ImageType::Invalid>
{
Image operator()(ImageType desiredType, const Image& inImage)
{
return Image(ImageType::Invalid);
};
};
And then in the non-templated API, I use these recursive templates, with a list of the the various options, and pass it the runtime-data value to match against, and the input image.
Image ConvertImage(ImageType desiredType, const Image& inImage)
{
return _maker<ImageType::RGB, ImageType::CMYK, ImageType::Grayscale, ImageType::Invalid>()(desiredType, inImage);
};
Called like this:
Image x = Image(ImageType::RGB);
Image y = ConvertImage(ImageType::Grayscale, x);
if (x.img_type == y.img_type)
{
cout << "not converted\n";
}
else
{
cout << "converted\n";
}
What we end up with, at the deepest point is a backtrace like this:
So this mostly achieved my goal of getting rid of the switch statement. It would be nice if it were a little cleaner (i.e. I didn't have to jump through hoops to stop at the bottom of the recursion) or if it weren't recursive. But this C++11 variadic template stuff was already kind of pressing my luck.
Hopefully some journeyman template metaprogrammer has a better idea.