Reputation: 183
I am working on a fairly tightly coupled library which up until now has explicitly assumed all computations are done with doubles. I'm in the process of converting some of the core classes to templates so that we can start computing with std::complex<double>
. I've templated about 10 of our classes so far have noticed a tendency toward proliferation of templates. As one class becomes templated, any other class that uses the templated class appears to need templating as well. I think I can avoid some of this proliferation by defining abstract base classes for my templates so that other classes can just use pointers to the abstract class and then refer to either a double
or std::complex<double>
version of the derived class. This seems to work on at the header level, but when I dive into the source files, the templated class will often have functions which compute a value or container of values of type double
or std::complex<double>
. It seems like a waste to template a whole class just because a couple of lines in the source file are different because of some other classes return type.
The use of auto
seems like a possible way to fix this, but I'm not 100% sure it would work. Suppose I have an abstract base class AbstractFunction
from which Function<Scalar>
derives, where Scalar
can be double
or std::complex<double>
. Now suppose we have two member functions:
virtual Scalar Function<Scalar>::value(double x);
virtual void Function<Scalar>::values(std::vector<Scalar> &values, std::vector<double> x);
And suppose I have some other class (that I don't want to template) with a member function that calls one of these.
// populate double x and std::vector<double> xs
auto value = functionPtr->value(x);
std::vector<auto> values;
functionPtr->values(values, xs);
// do something with value and values
where functionPtr
is of type std::shared_ptr<AbstractFunction>
.
I could see auto
working for the first case, but I don't believe I could construct a vector of auto
to be filled with the second one. Does this necessitate making the calling class a template? Can someone recommend another strategy to cut down on the proliferation of templates?
Upvotes: 2
Views: 528
Reputation: 56863
I think you are already wrong in assuming that the first use-case is going to work. If you have an abstract base class, then either value
is a member of it and you can call it through std::shared_ptr<AbstractFunction>
or value
is not a member of it and only available if you know the derived class' type. In the first case, the AbstractFunction::value
method must have a fixed return type, it can not depend on Scalar
, which is the template parameter of the derived class.
That said: In my experience the two concept often don't mix well. You either want to create an abstract base class with the full interface or you want a template. In the latter case, there is often no need / no benefit for having an abstract base class. It then follows that also the code using your template works with templates.
What might help you is to "export" the template parameter from Function
, i.e.
template<typename T>
class Function
{
public:
using value_type = T;
value_type value() const;
// ...
};
and in other parts of the code, use a template which takes any T
which behaves like Function
if you don't want to directly write out (and limit yourself) to Function
:
template<typename T>
void something( const std::shared_ptr<T>& functionPtr )
{
// ignoring where x comes from...
using V = typename T::value_type;
V value = functionPtr->value(x);
std::vector<V> values;
functionPtr->values(values, xs);
}
Note that this is just one option, I don't know if it is the best option for your use-case.
Upvotes: 1