Re: Alternative pattern for virtual template
On 5 mai, 17:24, Noah Roberts <n...@nowhere.com> wrote:
GeeRay wrote:
Hi all,
I have this problem: I need to implement a class hierarchy where the
main class has a template method that will be inherited by the subclass.
Something like:
class SuperClass
{
public:
template<typename T>
virtual void foo(const T& t) const = 0;
}
class SubClass: public SuperClass
{
public:
template<typename T>
virtual void foo(const T& t) const { cout << t << endl;}
}
Considering I know why it is not possible to make a template virtual, is
there an alternative pattern to solve this kind of problems.
No. There are some things you can do but none of them generate the
equivalent of virtual template functions. Simple fact is that the
language doesn't support such a construct. How could it?
I find that any time I'm tempted to do this, my design is not exactly
correct.
The closest you might come is to use NVPI and account for every possible
argument to your template by hand:
struct Base
{
// Document the concept:
// T must be int or double.
template < typename T >
void f(T const& x);
protected:
virtual void do_f(double const& x) { ... }
virtual void do_f(int const& x) { ... }
};
template < >
void Base::f<double>(double const& x) { do_f(x); }
template < >
void Base::f<int>(int const& x) { do_f(x); }
Basically you're doing what the compiler would have to magically do if
it were to attempt supporting virtual template functions. As you can
see, pretty much impossible without full and future knowledge of all
calls to f<>(), which is why it isn't done.
I'm sure you could make some preprocessor stuff to construct much of the
above code, but you're still going to have to account for all possible
types passed to your function.
I'd suggest looking at alternative approaches.
On of this approach is to use dynamic polymorphism, that is if you can
define a same contract on your type. As an example if your foo is only
displaying a message with the data in parameter, your contract if that
the type should support (ostream<<T), you define the interface:
struct foo_ostreamable
{
virtual ostream& output(ostream&)const=0;
};
Then you define a templated foo_streambable implementation:
template<typename T>
struct foo_ostreamable_imp: foo_ostreamable
{
T value;
foo_ostreamable_imp(const T& t):value(t){}
virtual ostream& output(ostream& os)const{return os<<value;}
};
And finally the parameter of SuperClass::foo with templated
constructor:
struct foo_param
{
template<typename T>
foo_param(const T& t){data.reset(new foo_ostreamable_imp<T>(t));
scoped_ptr<foo_ostreamable> data;
};
// foo_param can be written into ostream
ostream& operator<<(ostream& os,const foo_param& p)
{
return p.data->output(os);
}
And finally, you define your classes:
class SuperClass
{
public:
virtual void foo(const foo_param&) const = 0;
};
class SubClass: public SuperClass
{
public:
virtual void foo(const foo_param& p) const { cout << p <<
endl;}
} ;
The compiler will automatically resolve with correct subtype:
SubClass s;
s.foo(1);
s.foo(0.1);
s.foo("bar");
....
If you want to keep/compare foo_param values, there is some management
to do such as deep copy and others to put it into canonical form. If a
type doesn't react as you want, you overload foo_param constructor.
There is an overhead but that depends on what you want.
--
Michael