C++ Covariant Templates

C++ covariant templates

Both the copy constructor and the assignment operator should be able to take a SmartPtr of a different type and attempt to copy the pointer from one to the other. If the types aren't compatible, the compiler will complain, and if they are compatible, you've solved your problem. Something like this:

template<class Type> class SmartPtr
{
....
template<class OtherType> SmartPtr(const SmartPtr<OtherType> &blah) // same logic as the SmartPtr<Type> copy constructor

template<class OtherType> SmartPtr<Type> &operator=(const SmartPtr<OtherType> &blah) // same logic as the SmartPtr<Type> assignment operator
};

C++ Template Covariance

Given the reference to an earlier question as a clarification device, it seems you are asking why T<Derived> is not usually derived from T<Base>.

Consider T = std::shared_ptr.

You don't want to be able to do this:

void foo( shared_ptr<Base>& p ) { p.reset( new Derived2 ); }

auto main() -> int
{
shared_ptr<Derived1> p;
foo( p ); // Oops, p now points to unrelated Derived2
}

C++ Templates, Polymorphism, and Template Covariance

If each class reports its base:

class A {
using Base = void;
// things
}

class B : public A {
using Base = A;
// more things
}

You could do

template<typename T>
class Wrapper : public EmptyForVoid<T::Base>::Type

And use EmptyForVoid for the base class selection:

template<typename T>
struct EmptyForVoid { using Type = T; };

template<>
struct EmptyForVoid<void> { struct Type {}; };

This will make Wrappers follow the same inheritance tree as the types they wrap.

Specifying covariant/contravariant behavior with C++ templates

In C++, like in other OO languages, subtypes are generally represented by inheritance.

However, we cannot model covariance and contravariance relationships through inheritance, because there is no way to list the base classes of a class (without any of the various reflection proposals, which have not made their way into the language yet).

The easiest way to allow this kind of behavior is to allow covariant and contravariant templated classes to convert based on the relationship of the related types.

Covariance

If Derived is-a Base, then Covariant<Derived> "should-be-a" Covariant<Base>.

Normally, the solution would be to make Covariant<Derived> inherit from Covariant<Base>, but we currently have no way of finding Base given only Derived. However, we can enable the conversion by writing a constructor for Covariant<Base> taking any Covariant<Derived>:

template <typename T>
struct Covariant {
template <typename Derived>
Covariant(const Covariant<Derived>& derived,
std::enable_if_t<std::is_base_of_v<T, Derived>>* = nullptr)
{
/* Do your conversion here */
}
};

Contravariance

If Derived is-a Base, then Contravariant<Base> "should-be-a" Contravariant<Derived>

The trick here is much the same - allowing the conversion of any Contravariant<Base> to Contravariant<Derived>:

template <typename T>
struct Contravariant {
template <typename Base>
Contravariant(const Contravariant<Base>& base,
std::enable_if_t<std::is_base_of_v<Base, T>>* = nullptr)
{
/* Do your conversion here */
}
};

However

This has one major drawback: You need to implement the conversions manually, and be aware that accidental object slicing may ruin your ability to convert back (e.g. if you define a covariant container type, that will be a cause of major headaches).

Essentially, until reflection allows us to automate that sort of inheritance relationship, conversions are the only way to do this, and I would not recommend using this for anything complex. As soon as you store objects of your T in the covariant/contravariant classes, you are in for a world of hurt.

Here is a Godbolt link to show that it works

Correct way to define a covariant template function in C++

Depends on the check you actually want to do.

To check for a public, unambiguous base, use is_convertible on pointers:

template <class T>
auto foo(T& x) -> std::enable_if_t<std::is_convertible<T*, A*>{}/*, void*/> {
// stuff
}

To check for any base whatsoever, public, protected or private, ambiguous or unambiguous, use is_base_of:

template <class T>
auto foo(T& x) -> std::enable_if_t<std::is_base_of<A, T>{}/*, void*/> {
// stuff
}

c++ template covariance polymorphism

A simple technical solution is to parameterise the concrete factory with the desired base, like this:

template <class C, class C_base = C>
class NormalFactory:public Factory<C_base>{
public:

C* create(){return new C;}
};

Example at Coliru.

However, the design doesn't feel right somehow. I suggest thinking about what the factories are meant to accomplish, and if there's any other more natural way of accomplishing that in C++. I.e., I think this is an XY-question.

C++ containers, covariance and template

You can let Derived::getData() return QVector<Data*>. When you need to use it, find out if the pointers in QVector is to Data or DerivedData, using dynamic_cast or similar method.

How to implement template class covariance in C++?

The template argument has nothing to do with the content of the object you are pointing to. There is no reason this should work. To illustrate

struct Base { };
struct Derived : Base {};

template<typename T> struct A { int foo; };
template<> struct A<Base> { int foo; int bar; };

A<Derived> a;
A<Base> *b = &a; // assume this would work
b->bar = 0; // oops!

You will eventually access integer bar that doesn't really exist in a!


OK, now that you provided some more information, it's clear you want to do something completely different. Here is some starter:

template<typename T>
struct MyIterator : std::iterator<...> {
MyIterator():ibase() { }
template<typename U>
MyIterator(U u):ibase(new Impl<U>(u)) { }
MyIterator(MyIterator const& a):ibase(a.ibase->clone())

MyIterator &operator=(MyIterator m) {
m.ibase.swap(ibase);
return *this;
}

MyIterator &operator++() { ibase->inc(); return *this; }
MyIterator &operator--() { ibase->dec(); return *this; }
T &operator*() { return ibase->deref(); }
// ...

private:
struct IBase {
virtual ~IBase() { }
virtual T &deref() = 0;
virtual void inc() = 0;
virtual void dec() = 0;
// ...

virtual IBase *clone() = 0;
};
template<typename U>
struct Impl : IBase {
Impl(U u):u(u) { }
virtual T &deref() { return *u; }
virtual void inc() { ++u; }
virtual void dec() { --u; }
virtual IBase *clone() { return new Impl(*this); }
U u;
};

boost::scoped_ptr<IBase> ibase;
};

Then you can use it as

MyIterator<Base> it(l.begin());
++it;
Base &b = *it;

You may want to look into any_iterator. With a bit of luck, you can use that template for your purpose (I haven't tested it).



Related Topics



Leave a reply



Submit