Reputation: 490133
I've put together two code excerpts of template specialisation uses that I had come across that I find particularly strange. I would even call them needlessly flashy.
In general I have my doubts that templates are actually the best way of designing these objects (especially the first case).
Which would be the better approach and why? Or is there a totally different approach that is better?
1) Templates as a replacement of passing pointers to functions:
//fusion_manager.h
template <typename InputFilterAlgorithm,
typename PredictionAlgorithm,
typename AssociationAlgorithm,
typename FusionAlgorithm>
class FusionManager
{
public:
FusionManager(Environment& env);
...
private:
Environment& env_m;
InputFilterAlgorithm filter_alg_m;
PredictionAlgorithm prediction_alg_m;
AssociationAlgorithm association_alg_m;
FusionAlgorithm fusion_alg_m;
...
};
//fusion_manager.cpp
template <typename InputFilterAlgorithm,
typename PredictionAlgorithm,
typename AssociationAlgorithm,
typename FusionAlgorithm>
FusionManager<InputFilterAlgorithm,
PredictionAlgorithm,
AssociationAlgorithm,
FusionAlgorithm,
TrackExtendedDataType>::FusionManager(Environment& env)
:
env_m(env),
filter_alg_m(env),
prediction_alg_m(env),
association_alg_m(env),
fusion_alg_m(env)
{
...
}
//main.cpp
...
FusionManager<TestInputFilterAlgorithm,
TestPredictionAlgorithm,
TestAssociationAlgorithm,
TestFusionAlgorithm> fusionManager(env);
...
...Instead of using something like this:
//fusion_manager.h
class FusionManager
{
public:
//Let's say each algorithm is encapsulated by a class
FusionManager(Environment& env,
InputFilterAlgorithm&&,
PredictionAlgorithm&&,
AssociationAlgorithm&&,
FusionAlgorithm&&);
private:
Environment& env_m;
InputFilterAlgorithm filter_alg_m;
PredictionAlgorithm prediction_alg_m;
AssociationAlgorithm association_alg_m;
FusionAlgorithm fusion_alg_m;
};
//fusion_manager.cpp
FusionManager::FusionManager(Environment& env,
InputFilterAlgorithm&& filter_alg,
PredictionAlgorithm&& prediction_alg,
AssociationAlgorithm&& association_alg,
FusionAlgorithm&& fusion_alg)
:
env_m(env),
filter_alg_m(std::move(filter_alg)),
prediction_alg_m(std::move(prediction_alg)),
association_alg_m(std::move(association_alg)),
fusion_alg_m(std::move(fusion_alg))
{
...
}
//main.cpp
...
FusionManager<TestInputFilterAlgorithm,
TestPredictionAlgorithm,
TestAssociationAlgorithm,
TestFusionAlgorithm> fusionManager(env);
...
2) Using templates as a replacement of inheritance and virtual methods:
//factorization.h
template<typename ProbabilityDistributionType>
class Factorization
{
...
public:
ProbabilityDistributionType factorize();
private:
std::Vector<ProbabilityDistributionType> factors_m;
...
};
//factorization.cpp
template<>
CPD Factorization<CPD>::factorize()
{
for (auto & factor : factors_m)
{
factor.factorize();//This will call the factorize method of CPD
}
}
template<>
JointProbDistr Factorization<JointProbDistr>::factorize()
{
for (auto & factor : factors_m)
{
factor.factorize();//This will call the factorize method of JointProbDistr
}
}
Instead of using something like this:
//factorization.h
template<typename ProbabilityDistributionType>
class Factorization
{
...
public:
virtual ProbabilityDistributionType factorize() = 0;
private:
std::Vector<ProbabilityDistributionType> factors_m;
...
};
//cpd_factorization.h
class CPDFactorization : public Factorization<CPD>
{
...
public:
CPD factorize();//Implementing the parent's pure virtual method. This will call the factorize method of CPD
};
//jointprobdistr_factorization.h
class CPDFactorization : public Factorization<JointProbDistr>
{
...
public:
JointProbDistr factorize();//Implementing the parent's pure virtual method. This will call the factorize method of JointProbDistr
};
Upvotes: 3
Views: 1860
Reputation: 40594
Using a template has both the advantage and the disadvantage of the compiler seeing the fully specialized implementation where the template is used.
This is an advantage because it allows the compiler to optimize more aggressively: It can inline as much as it wants, and resolve any shifting of data between function invocations, removing all the cruft that might have been introduced by the programmer in an effort to structure their source code in a convenient way.
This is a disadvantage because it forces the templated decisions to be compile-time decisions. If a variable is a template argument, it cannot depend on input given at runtime. Imagine what would happen if the length of std::string
were a template argument: String manipulation would be as flexible as in FORTRAN. You absolutely don't want this. Of course, some things should be known at compile time, and it's very ok to have them as template arguments. But if you overdo templates, you get unnecessarily rigid code. (Been there, done that, learned to avoid it.)
This is also a disadvantage because it forces recompilation of all uses of a template when its implementation changes. If your program is all templates except for main, you have to recompile everything for every little change. If you use pointers to functions, virtual functions, and/or functors, calling sites don't need to be recompiled when the implementation of the called function changes. In a properly setup build system, they would depend on the header which won't change unless there are breaking interface changes. For me, the consequence of this is, that all my templates should be small, self contained bits of code which do not depend on other templates in several layers.
All in all, templates are a great tool, and a very easily overused tool at the same time. Try to not use them when you don't get any real advantage from it.
Upvotes: 5
Reputation: 66371
The first one can be used with anything callable - function pointers, std::function
, etc.
Your suggestion is very limited type-wise.
The second avoids virtual calls, which is useful in situations where you want to avoid that, and gives more opportunities for inlining.
In short: the first one uses templates for flexibility, the second for performance.
Upvotes: 3