Templates Polymorphism

Templates polymorphism

I think the exact terminology for what you need is "template covariance", meaning that if B inherits from A, then somehow T<B> inherits from T<A>. This is not the case in C++, nor it is with Java and C# generics*.

There is a good reason to avoid template covariance: this will simply remove all type safety in the template class. Let me explain with the following example:

//Assume the following class hierarchy
class Fruit {...};

class Apple : public Fruit {...};

class Orange : public Fruit {...};

//Now I will use these types to instantiate a class template, namely std::vector
int main()
{
std::vector<Apple> apple_vec;
apple_vec.push_back(Apple()); //no problem here

//If templates were covariant, the following would be legal
std::vector<Fruit> & fruit_vec = apple_vec;

//push_back would expect a Fruit, so I could pass it an Orange
fruit_vec.push_back(Orange());

//Oh no! I just added an orange in my apple basket!
}

Consequently, you should consider T<A> and T<B> as completely unrelated types, regardless of the relation between A and B.

So how could you solve the issue you're facing? In Java and C#, you could use respectively bounded wildcards and constraints:

//Java code
Bar(Container<? extends Interface) {...}

//C# code
Bar<T>(Container<T> container) where T : Interface {...}

The next C++ Standard (known as C++1x (formerly C++0x)) initially contained an even more powerful mechanism named Concepts, that would have let developers enforce syntaxic and/or semantic requirements on template parameters, but was unfortunately postponed to a later date. However, Boost has a Concept Check library that may interest you.

Nevertheless, concepts might be a little overkill for the problem you encounter, an using a simple static assert as proposed by @gf is probably the best solution.

* Update: Since .Net Framework 4, it is possible to mark generic parameters has being covariant or contravariant.

mixing templates with polymorphism

You have a few choices. I'll explain my preferred solution first.

1. Use dynamic dispatch

If you have an array of a base class type, why do you even want to do stuff with Var? That variable is specific to the child class. If you have a A somewhere, you shouldn't even care what B has or hasn't at that place.

Operations on the typed variable should be encapsulated in virtual function in the base class. If you want to do condition and stuff, maybe you could encapsulate that condition into a virtual function that returns a boolean.

2a. Drop the base class and use variant

Sometimes, you know in advance the amount of types that will go into that list. Using a variant and drop the base class is a good solution that may apply to your case.

Let's say you only have int, double and std::string:

using poly = std::variant<B<int>, B<double>, B<std::string>>;

std::array<poly, 3> arr;

arr[0] = B<int>{};
arr[1] = B<double>{};
arr[2] = B<std::string>{};
// arr[2] = B<widget>{}; // error, not in the variant type

std::visit(
[](auto& b) {
using T = std::decay_t<decltype(b)>;
if constexpr (std::is_same_v<B<int>, T>) {
b.Var = 2; // yay!
}
},
arr[0]
);

2b. Drop the base class and use generic functions

Drop the base class entirely, and template your functions that do operation on them. You can move all your function into an interface or many std::function. Operate on that instead of the function directly.

Here's an example of what I meant:

template<typename T>
void useA(T const& a) {
a.Var = 34; // Yay, direct access!
}

struct B {
std::function<void()> useA;
};

void createBWithInt() {
A<int> a;
B b;

b.useA = [a]{
useA(a);
};
};

This is fine for cases where you only have few operations. But it can quickly lead to code bloat if you have a lot of operations or if you have many types of std::function.

3. Use a visitor

You could create a visitor that dispatch to the right type.

This solution would be much close to what you except, but is quite combersome and can break easily when adding cases.

Something like this:

struct B_Details {
protected:
struct Visitor {
virtual accept(int) = 0;
virtual void accept(double) = 0;
virtual void accept(std::string) = 0;
virtual void accept(some_type) = 0;
};

template<typename T>
struct VisitorImpl : T, Visitor {
void accept(int value) override {
T::operator()(value);
}

void accept(double) override {
T::operator()(value);
}

void accept(std::string) override {
T::operator()(value);
}

void accept(some_type) override {
T::operator()(value);
}
};
};

template<typename T>
struct B : private B_Details {
template<typename F>
void visit(F f) {
dispatch_visitor(VisitorImpl<F>{f});
}

private:
virtual void dispatch_visitor(Visitor const&) = 0;
};

// later

B* b = ...;

b->visit([](auto const& Var) {
// Var is the right type here
});

Then of course, you have to implement the dispatch_visitor for each child class.

4. Use std::any

This is litteraly returning the variable with type erasure. You cannot do any operation on it without casting it back:

class A {
std::any GetVar()
};

I personnaly don't like this solution because it can break easily and is not generic at all. I would not even use polymorphism in that case.

Can template polymorphism be used in place of OO polymorphism?

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.

what is the difference between templates and polymorphism

You seem to misunderstand what polymorphism is.

Polymorphism, at its core, has nothing to do with derived classes. Polymorphism simply means the ability to use a type without knowing everything about it. Rather than using a concrete type, polymorphism relies on some form of prototype to define what types it takes. Any types that fit that prototype are accepted.

Runtime polymorphism, in C++, is provided by deriving classes from a base class that contains virtual functions. The base class and virtual functions form the polymorphic prototype. Code written to accept the base class that calls these virtual functions will accept any class instance derived from the base class.

Compile-time polymorphism is polymorphism that happens... at compile time ;) What this means is that the compiler must know what is going on. You may have written the C++ code against a polymorphic prototype, but the compiler doesn't care. You get specific concrete types post-compilation.

Compile-time polymorphism is provided by templates in C++. A template function or class can take any type which conforms to a prototype, usually called a "concept". Unlike base classes and virtual functions, the prototype is implicit: the prototype is defined only by how the type is used by the template function/class.

If you have this template function:

template<typename T>
void Stuff(T &t)
{
t.call(15);
}

There is an implicit requirement on T. This requirement is that it has a member function called call. There must be a single overload of this member function which can be called with an integer value.

This means that any type that happens to fit this prototype can be used.

Template polymorphism is more broad than inheritance polymorphism, because it can be used by a broader array of types. A type has to be designed specifically to use inheritance polymorphism; you have to derive from a class. A type can be non-destructively (ie: you don't have to change the type itself) adapted to template polymorphism. Even moreso if your template prototype is well designed:

template<typename T>
void Stuff(T &t)
{
call(t, 15);
}

All that this version of Stuff requires is that there is some function that takes a T& and an integer value. If I have some type that I want to use with Stuff, all I have to do is define a call function in an appropriate namespace (namely, the namespace that the type was defined in). And this will work just fine. All without modifying the type itself.

Of course, compile-time polymorphism is... compile-time. If I want some user input or data file to select the polymorphic type, templates aren't going to help a whole lot (though type erasure, a template-based technique, can help). The principle benefit of runtime polymorphism is that it is indeed runtime.

Another benefit is that it is more precise about its prototypes. Everything is explicitly stated about inheritance. The virtual function interface in a base class is clearly laid out. The compiler will stop you from attempting to use that base class incorrectly (calling methods that don't exist on it). Indeed, a decent IDE will guide your code so that you will only see the methods on the base class.

Template polymorphism is a lot more implicit. Since C++ has no way of spelling out the prototype that a particular template function/class puts on a type, it's very easy to accidentally call something on a template type that you shouldn't. The compiler will only detect this when you try to use a type that doesn't fit the prototype. And even then you will generally get a massive error spew (depending on how deeply nested your template code is) that makes it difficult to know where the problem is.

It's also a lot harder to implement the implicit template polymorphic prototype, since it isn't spelled out. Implementing a derived class requires walking through the base class, looking at all of the virtual functions, and implementing them. Doing this for a template prototype is much more difficult, unless there is documentation somewhere that spells it out. If you fail to implement something, you again get an error spew that is generally less than forthcoming about the problem.

C++ Templates polymorphism used togheter

It depends on what you want to do in CameraGui. If it does not depend on the template parameter type of Camera, you could do something like the following:

class BasicCamera {...};
template <typename T> class BufferCalculation {...}
template <typename T> class Camera : public BasicCamera { BufferCalculation<T> bufferCalculation; }
class Camera1 : public Camera<unsigned char> {}
class Camera2 : public Camera<unsigned short> {}
class CameraGui { BasicCamera* camera; }

If the operations in CameraGui depend on the type, you can either make it itself a template class, or make the operations take/return a hierarchy of objects deriving from a non-template type as well.

C++ runtime polymorphism with templates

The only issue is this: You must store a pointer to the base class AnimalObserver in your Observable class. You can do this in a number of ways. unique_ptr<> comes to mind.

Here's an implementation with some hints along the way. godbolt: https://godbolt.org/z/1Gq35G

#include <list>
#include <iostream>
#include <memory>
using namespace std;

struct AnimalObserver
{
virtual ~AnimalObserver() { }
// Hint 1: Declare abstract so that it won't even compile if you use the
// base class directly in a collection - i.e. and not a pointer to the base.
virtual void doSomething() const = 0;
};

template <typename E>
class Observable {
private:
typedef unique_ptr< E > _TyPtrContained;
std::list<_TyPtrContained> observers;

public:
void addObserver( unique_ptr< E > && rrobserver ) {
// Remember that inside a method that receives a rvalue-reference,
// it is an lvalue-reference and must be std::move()'d when passing to a
// method that accepts an rvalue-reference.
observers.push_back( std::move( rrobserver ) );
}

void notifyAll() {
for (auto & observer: observers) {
observer->doSomething();
}
}
};

class DogObserver : public AnimalObserver {
public:
DogObserver() = default;
// Hint 2: Use override to ensure that you are overriding an actual
// virtual and not just declaring a method with a different signature
// than the base.
void doSomething() const override {
std::cout << "woof!";
}
};

class CatObserver : public AnimalObserver {
public:
CatObserver() = default;
void doSomething() const override {
std::cout << "meow!";
}
};

int
main()
{
auto observable = Observable<AnimalObserver>();
// Hint 3: You can assign a unique_ptr<derived> to a unique_ptr<base>.
unique_ptr< AnimalObserver > cat = make_unique< CatObserver >();
unique_ptr< AnimalObserver > dog = make_unique< DogObserver >();

// We move the local unique_ptrs into the addObserver method so that
// they are then moved into the list using the rvalue-reference version
// of list<>::push_back().
observable.addObserver( std::move( cat ) );
observable.addObserver( std::move( dog ) );

observable.notifyAll();
// And, voila, things work...
}

Can static polymorphism (templates) be used despite type erasure?

This is a classical double dispatch problem. The usual solution to this problem is to have some kind of dispatcher class with multiple implementations of the function you want to dispatch (get in your case). This is called the visitor pattern. The well-known drawback of it is the dependency cycle it creates (each class in the hierarchy depends on all other classes in the hierarchy). Thus there's a need to revisit it each time a new type is added. No amount of template wizardry eliminates it.

You don't have a specialised Visitor class, your Variable serves as a Visitor of itself, but this is a minor detail.

Since you don't like this solution, there is another one. It uses a registry of functions populated at run time and keyed on type identification of their arguments. This is sometimes called "Acyclic Visitor".

Here's a half-baked C++11-friendly implementation for your case.

#include <map>
#include <vector>
#include <typeinfo>
#include <typeindex>
#include <utility>
#include <functional>
#include <string>
#include <stdexcept>

struct Variable
{
virtual void convertValue(Variable& to) const = 0;
virtual ~Variable() {};

virtual std::type_index getTypeIdx() const = 0;

template <typename K> K get() const;

static std::map<std::pair<std::type_index, std::type_index>,
std::function<void(const Variable&, Variable&)>>
conversionMap;

template <typename T, typename K>
static void registerConversion(K (*fn)(const T&));
};


template <typename T>
struct VariableImpl : Variable
{
T value;

VariableImpl(const T &v) : value{v} {};
VariableImpl() : value{} {}; // this is needed for a declaration of
// `VariableImpl<K> below
// It can be avoided but it is
// a story for another day

void convertValue(Variable& to) const override
{
auto typeIdxFrom = getTypeIdx();
auto typeIdxTo = to.getTypeIdx();

if (typeIdxFrom == typeIdxTo) // no conversion needed
{
dynamic_cast<VariableImpl<T>&>(to).value = value;
}
else
{
auto fcnIter = conversionMap.find({getTypeIdx(), to.getTypeIdx()});
if (fcnIter != conversionMap.end())
{
fcnIter->second(*this, to);
}
else
throw std::logic_error("no conversion");
}
}

std::type_index getTypeIdx() const override
{
return std::type_index(typeid(T));
}
};

template <typename K> K Variable::get() const
{
VariableImpl<K> vk;
convertValue(vk);
return vk.value;
}

template <typename T, typename K>
void Variable::registerConversion(K (*fn)(const T&))
{
// add a mutex if you ever spread this over multiple threads
conversionMap[{std::type_index(typeid(T)), std::type_index(typeid(K))}] =
[fn](const Variable& from, Variable& to) {
dynamic_cast<VariableImpl<K>&>(to).value =
fn(dynamic_cast<const VariableImpl<T>&>(from).value);
};
}

Now of course you need to call registerConversion e.g. at the beginning of main and pass it each conversion function.

Variable::registerConversion(int_to_string);
Variable::registerConversion(string_to_int);

This is not ideal, but hardly anything is ever ideal.


Having said all that, I would recommend you revisit your design. Do you really need all these conversions? Why not pick one representation and stick with it?



Related Topics



Leave a reply



Submit