Can template polymorphism be used in place of OO p

2019-01-31 08:18发布

I am trying to get my head around applying template programming (and at some future point, template metaprogramming) to real-world scenarios. One problem I am finding is that C++ Templates and Polymorphism don't always play together the way I want.

My question is if the way I'm trying to apply template programming is improper (and I should use plain old OOP) or if I'm still stuck in the OOP mindset.

In this particular case, I am trying to solve a problem using the strategy-pattern. I keep running into the problem where I end up wanting something to behave polymorphically which templates don't seem to support.

OOP Code using composition:

class Interpolator {
   public:
     Interpolator(ICacheStrategy* const c, IDataSource* const d);
     Value GetValue(const double);
}

void main(...) {
    Interpolator* i;
    if(param==1)
       i = new Interpolator(new InMemoryStrategy(...), new TextFileDataSource(...));
    else if(param==2)
       i = new Interpolator(new InMemoryStrategy(...), new OdbcDataSource(...));
    else if(param==3)
       i = new Interpolator(new NoCachingStrategy(...), new RestDataSource(...));

    while(run) {
       double input = WaitForRequest();
       SendRequest( i->GetValue(input));
    }
}

Potential Template Version:

class Interpolator<class TCacheStrategy, class TDataSource> {
   public:
     Interpolator();
     Value GetValue(const double);               //may not be the best way but
     void ConfigCache(const& ConfigObject);      //just to illustrate Cache/DS         
     void ConfigDataSource(const& ConfigObject); //need to configured

}

//Possible way of doing main?
void main(...) {
    if(param==1)
       DoIt(Interpolator<InMemoryStrategy,TextFileDataSource>(),c,d);
    else if(param==2)
       DoIt(Interpolator<InMemoryStrategy,OdbcDataSource>(),c,d)
    else if(param==3)
       DoIt(Interpolator<NoCachingStrategy,RestDataSource>(),c,d)

}

template<class T>
void DoIt(const T&  t, ConfigObject c, ConfigObject d) {
   t.ConfigCache(c);
   t.ConfigDataSource(c);
   while(run) {
      double input = WaitForRequest();
      SendRequest( t.GetValue(input));
   }
}

When I try to convert the OOP implementation to a template-based implementation, the Interpolator code can be translated without a lot of pain. Basically, replace the "interfaces" with Template type parameters, and add a mechanism to either pass in an instance of Strategy/DataSource or configuration parameters.

But when I get down to the "main", it's not clear to me how that should be written to take advantage of templates in the style of template meta programming. I often want to use polymorphism, but it doesn't seem to play well with templates (at times, it feels like I need Java's type-erasure generics... ugh).

When I often find I want to do is have something like TemplateType<?,?> x = new TemplateType<X,Y>() where x doesn't care what X,Y is.

In fact, this is often my problem when using templates.

  1. Do I need to apply one more level of templates?
  2. Am I trying to use my shiny new power template wrench to install a OOP nail into a PCI slot?
  3. Or am I just thinking of this all wrong when it comes to template programming?

[Edit] A few folks have pointed out this is not actually template metaprogramming so I've reworded the question slightly. Perhaps that's part of the problem--I have yet grok what TMP really is.

3条回答
beautiful°
2楼-- · 2019-01-31 08:46

Templates provide static polymorphism: you specify a template parameter at compile time implementing the strategy. They don't provide dynamic polymorphism, where you supply an object at runtime with virtual member functions that implement the strategy.

Your example template code will create three different classes, each of which contains all the Interpolator code, compiled using different template parameters and possibly inlining code from them. That probably isn't what you want from the POV of code size, although there's nothing categorically wrong with it. Supposing that you were optimising to avoid function call overhead, then it might be an improvement on dynamic polymorphism. More likely it's overkill. If you want to use the strategy pattern dynamically, then you don't need templates, just make virtual calls where relevant.

You can't have a variable of type MyTemplate<?> (except appearing in another template before it's instantiated). MyTemplate<X> and MyTemplate<Y> are completely unrelated classes (even if X and Y are related), which perhaps just so happen to have similar functions if they're instantiated from the same template (which they needn't be - one might be a specialisation). Even if they are, if the template parameter is involved in the signatures of any of the member functions, then those functions aren't the same, they just have the same names. So from the POV of dynamic polymorphism, instances of the same template are in the same position as any two classes - they can only play if you give them a common base class with some virtual member functions.

So, you could define a common base class:

class InterpolatorInterface {
public:
    virtual Value GetValue(const double) = 0;
    virtual void ConfigCache(const& ConfigObject) = 0;
    virtual void ConfigDataSource(const& ConfigObject) = 0;
    virtual ~InterpolatorInterface() {}
};

Then:

template <typename TCacheStrategy, typename TDataSource>
class Interpolator: public InterpolatorInterface {
    ...
};

Now you're using templates to create your different kinds of Interpolator according to what's known at compile time (so calls from the interpolator to the strategies are non-virtual), and you're using dynamic polymorphism to treat them the same even though you don't know until runtime which one you want (so calls from the client to the interpolator are virtual). You just have to remember that the two are pretty much completely independent techniques, and the decisions where to use each are pretty much unrelated.

Btw, this isn't template meta-programming, it's just using templates.

Edit. As for what TMP is, here's the canonical introductory example:

#include <iostream>

template<int N>
struct Factorial {
    static const int value = N*Factorial<N-1>::value;
};

template<>
struct Factorial<0> {
    static const int value = 1;
};

int main() {
    std::cout << "12! = " << Factorial<12>::value << "\n";
}

Observe that 12! has been calculated by the compiler, and is a compile-time constant. This is exciting because it turns out that the C++ template system is a Turing-complete programming language, which the C preprocessor is not. Subject to resource limits, you can do arbitrary computations at compile time, avoiding runtime overhead in situations where you know the inputs at compile time. Templates can manipulate their template parameters like a functional language, and template parameters can be integers or types. Or functions, although those can't be "called" at compile time. Or other templates, although those can't be "returned" as static members of a struct.

查看更多
够拽才男人
3楼-- · 2019-01-31 08:49

I find templates and polymorphism work well toegther. In your example, if the client code doesn't care what template parameters Interpolator is using then introduce an abstract base class which the template sub-classes. E.g.:

class Interpolator
{
public:
    virtual Value GetValue (const double) = 0;
};

template<class TCacheStrategy, class TDataSource>
class InterpolatorImpl : public Interpolator
{
public:
     InterpolatorImpl ();
     Value GetValue(const double);
};

void main()
{
    int param = 1;

    Interpolator* interpolator = 0;

    if (param==1)
        interpolator = new InterpolatorImpl<InMemoryStrategy,TextFileDataSource> ();
    else if (param==2)
        interpolator = new InterpolatorImpl<InMemoryStrategy,OdbcDataSource> ();
    else if (param==3)
        interpolator = new InterpolatorImpl<NoCachingStrategy,RestDataSource> ();

    while (true)
    {
        double input = WaitForRequest();
        SendRequest( interpolator->GetValue (input));
    }
}

I use this idiom quite a lot. It quite nicely hides the templatey stuff from client code.

Note, i'm not sure this use of templates really classes as "meta-programming" though. I usually reserve that grandiose term for the use of more sophisticated compile-time template tricks, esp the use of conditionals, recursive defintions etc to effectively compute stuff at compile time.

查看更多
欢心
4楼-- · 2019-01-31 09:01

Templates are sometimes called static (or compile-time) polymorphism, so yes, they can sometimes be used instead of OOP (dynamic) polymorphism. Of course, it requires the types to be determined at compile-time, rather than runtime, so it can't completely replace dynamic polymorphism.

When I often find I want to do is have something like TemplateType x = new TemplateType() where x doesn't care what X,Y is.

Yeah, that's not possible. You have to do something similar to what you have with the DoIt() function. Often, I think that ends up a cleaner solution anyway (you end up with smaller functions that do just one thing each -- usually a good thing). But if the types are only determined at runtime (as with i in the OOP version of your main function), then templates won't work.

But In this case, I think your template version solves the problem well, and is a nice solution in its own right. (Although as onebyone mentions, it does mean code gets instantiated for all three templates, which might in some cases be a problem)

查看更多
登录 后发表回答