Reputation: 10273
I know that you can store a bunch of Parent* objects in a vector. However, if the functions you need to call on the objects cannot be defined in the parent class (they depend on a template parameter of the subclass) then how are you supposed to retrieve the objects from the container?
In this example:
#include <iostream>
class ImageBase{};
template <typename TPixel>
class Image : public ImageBase
{
public:
TPixel GetPixel() const {TPixel a; return a;}
};
template<typename TImage>
void Output(const TImage* image)
{
std::cout << image->GetPixel();
}
int main(int, char *[])
{
// Works correctly
{
Image<float>* image = new Image<float>;
image->GetPixel();
Output(image);
}
{
ImageBase* image = new Image<float>;
Output(image);
}
return 0;
}
The Output(image); where 'image' is a ImageBase* fails (of course) because GetPixel is not defined in ImageBase. I know you can dynamic_cast<> to a bunch of types to figure out if the subclass matches any of them, but this list could very quickly get very long. The long list would be fine if it could reside in one place, but how would you make a function to do this? The function would take an ImageBase*, but what would it return?
returnType? GetSubclass(ImageBase* input)
{
if(dynamic_cast<Image<float>*>(input))
{
return Image<float>*;
}
else if(dynamic_cast<Image<int>*>(input))
{
return Image<int>*;
}
}
It seems reasonable to me to want to be able to call some template functions on subclasses that only vary in signature by their template parameter (as setup in this example), does it not?
In my real case, both Image and ImageBase are part of a library, so I cannot change them.
Upvotes: 0
Views: 124
Reputation: 283634
Visitor pattern to recover the type information, possibly with a templated helper implementing the visit
function.
First, let's make your algorithm into a polymorphic functor object:
struct Output
{
std::ostream& dest;
Output(std::ostream& destination) : dest(destination) {}
template<typename PixelType>
void operator()(const Image<PixelType>* image) const
{
dest << image->GetPixel();
}
};
Now, let's add a visitor interface:
struct ImageVisitor /* abstract */
{
virtual void Visit(Image<RGBQUAD>*) const = 0;
virtual void Visit(Image<RGBTRIPLE>*) const = 0;
virtual void Visit(Image<RGBQUAD16>*) const = 0;
virtual void Visit(Image<RGBTRIPLE16>*) const = 0;
virtual void Visit(Image<RGBQUADF>*) const = 0;
virtual void Visit(Image<RGBTRIPLEF>*) const = 0;
virtual void Visit(Image<RGBQUADD>*) const = 0;
virtual void Visit(Image<RGBTRIPLED>*) const = 0;
};
And a forwarder:
template<typename Functor>
struct ImageVisitorShim : ImageVisitor
{
Functor& fn;
ImageVisitorShim(Functor& algorithm) : fn(algorithm) {}
virtual void Visit(Image<RGBQUAD> *im) const { fn(im); }
virtual void Visit(Image<RGBTRIPLE> *im) const { fn(im); }
virtual void Visit(Image<RGBQUAD16> *im) const { fn(im); }
virtual void Visit(Image<RGBTRIPLE16> *im) const { fn(im); }
virtual void Visit(Image<RGBQUADF> *im) const { fn(im); }
virtual void Visit(Image<RGBTRIPLEF> *im) const { fn(im); }
virtual void Visit(Image<RGBQUADD> *im) const { fn(im); }
virtual void Visit(Image<RGBTRIPLED> *im) const { fn(im); }
};
And a factory:
template<typename Functor>
ImageVisitorShim<Functor> MakeImageVisitor(Functor& f) { return f; }
Now a visitor-compliant image wrapper:
struct VisitableImageBase
{
virtual void VisitWith(const ImageVisitor&) = 0;
};
template<typename PixelType>
struct VisitableImage : VisitableImageBase
{
unique_ptr<Image<PixelType>> content; // or shared or raw pointer, if ownership is elsewhere
VisitableImage(Image<PixelType>* im) : content(im) {}
virtual void VisitWith(const ImageVisitor& v) { v.Visit(content.get()); }
};
Finally, you are able to use a polymorphic vector of images!
vector<unique_ptr<VisitableImageBase>> images;
Output outputter(std::cout);
for( auto vim : images ) vim->VisitWith(MakeImageVisitor(outputter));
That was a lot of code, but the good thing is that new types can be added without affecting existing functors (just extend the shim) as long as the functor was implemented with a template. And not much code is needed to add more image processing functions (just a new template functor class, similar to Output
).
Upvotes: 3