Covariance can be a useful concept, e.g. when implementing the abstract factory design pattern. However, in modern C++, we should return smart pointers that are not recognized to be covariant like raw pointers by the compiler.

Abstract factory

I will not go into too much detail about the abstract factory pattern, as it is not the point of this article. You can best look it up in the “Gang of Four” book or on the web. For the code on this post I will borrow the very popular example also used on Wikipedia:

Consider an abstract factory returning equally abstract widgets for our GUI. Those widgets can be buttons, text fields, dropdown boxes etc. Depending on the GUI framework you use (e.g. differing by operating system), a concrete factory creates concrete implementations of the widgets.

Abstract factory with smart pointers

I have written about factories returning smart pointers earlier. For this example I’ll take the simpler alternative and use std::unique_ptr . Our basic code may look roughly like this:

#include <iostream> #include <memory> struct AbstractButton { virtual void click() = 0; virtual ~AbstractButton() = default; }; struct AbstractWidgetFactory { virtual std::unique_ptr<AbstractButton> createButton() const = 0; }; struct FancyButton : AbstractButton { void click() final override { std::cout << "You clicked me, I'm so fancy!

"; } }; struct FancyWidgetFactory : AbstractWidgetFactory { std::unique_ptr<AbstractButton> createButton() const final override { return std::make_unique<FancyButton>(); } }; int main() { std::shared_ptr<AbstractWidgetFactory> theWidgetFactory = std::make_shared<FancyWidgetFactory>(); auto theButton = theWidgetFactory->createButton(); theButton->click(); }

A need for covariance

Let’s assume that our factories get some more features. For example, we could have a functionality that creates a simple message window with an “OK” button.

std::unique_ptr<AbstractWindow> createMessageWindow(std::string const& text) { auto theWindow = theWidgetFactory->createWindow(); theWindow->addText(text); auto theButton = theWidgetFactory->createButton(); theButton->setText("OK"); theWindow->add(std::move(theButton)); return theWindow; }

This is pretty abstract, and given the proper interface on AbstractButton and AbstractWindow it is completely agnostic of the concrete classes we have. But what if there are specialties for message windows?

If we implement that algorithm in the FancyWidgetFactory we do not win much, because createButton still returns a unique_ptr<AbstractButton> . We know that it is in fact a FancyButton , but we can not use that unless we apply some ugly downcasts.

std::unique_ptr<AbstractWindow> FancyWidgetFactory::createMessageWindow(std::string const& text) final override { auto theWindow = createWindow(); theWindow->addText(text); auto theButton = createButton(); //unique_ptr<AbstractButton> static_cast<FancyButton*>(theButton.get())->doFancyStuff(); //EWW! theButton->setText("OK"); theWindow->add(std::move(theButton)); return theWindow; }

Covariance with raw pointers

In the olden days where open fire was considered more romantic than dangerous we would use raw pointers as return values from our factories. The callers would have to deal with ownership management, failing and burning down the house on a regular basis.

In those days, covariant return values were easy: A virtual function that returns a (raw) pointer can be overridden by a function that returns a pointer to a more derived class:

AbstractButton* OldschoolAbstractWidgetFactory::createButton(); FancyButton* OldschoolFancyWidgetFactory::createButton();

Since a FancyButton is an AbstractButton , this makes perfect sense, and the compiler knows that. With smart pointers it’s not so easy, since for the compiler they are only templates instantiated with two classes that happen to be related.

That relation does not transfer to the template instantiations, since it usually makes no sense. A std::vector<Base> is not related to a std::vector<Derived> as a Base* is related to a Derived* .

Achieving covariance with smart pointers

So now we know the problem. How do we solve it with the means the language allows us? Let’s analyze the situation:

We want `createButton` on an `AbstractWidgetFactory` to return something that holds a button. Which concrete button that will be depends on the concrete factory.

We want `createButton` on an `FancyWidgetFactory` to return something that holds a `FancyButton`, so we don’t need to cast.

We want to have smart pointers, but those are not considered covariant by the language.

The latter leads us to a simple conclusion: If we want the first two points to be true, createButton simply can not be virtual . The solution is, as it is so often, another layer of indirection. We can just give the factory classes a nonvirtual interface and let the virtual call take place in another function:

struct AbstractWidgetFactory { std::unique_ptr<AbstractButton> createButton() const { return doCreateButton(); } // ... private: virtual std::unique_ptr<AbstractButton> doCreateButton() const = 0; }; struct FancyWidgetFactory : AbstractWidgetFactory { std::unique_ptr<FancyButton> createButton() const { return std::make_unique<FancyButton>(); } // ... private: virtual std::unique_ptr<AbstractButton> doCreateButton() const final override { return createButton(); } };

We can now write the creation of our fancy message window without any ugly casts:

std::unique_ptr<AbstractWindow> createMessageWindow(std::string const& text) final override { auto theWindow = createWindow(); theWindow->addText(text); auto theButton = createButton(); //unique_ptr<FancyButton> theButton->doFancyStuff(); //no more casts theButton->setText("OK"); theWindow->add(std::move(theButton)); return theWindow; }

All this just works because std::unique_ptr to derived classes can always be converted into std::unique_ptr to their base class. Since this also applies to std::shared_ptr the same pattern can be used to achieve covariance with those.

But there’s a problem

As was discussed by rhalbersma in the comments, having the nonvirtual createButton method redefined in the derived class can lead to several issues. The most important one is that the behavior can be surprising to users, which never is a good thing.

The straight forward solution is to rename the method in the derived class, e.g. createFancyButton . That way the overall functionality remains, albeit it is more explicit and less surprising. This may not be the “true form” of covariance any more, but that are the kind of compromises we have to make.

You can find the full code on my GitHub repository.

Conclusion

If you really need covariance with smart pointers, it is manageable, although you have to add that extra layer of indirection. There should be better alternatives though, since C++ is not (only) an object oriented language.

There obviously is no perfect solution to the problem, but I hope I could show a possible approach to problems like this: If there is no single functionality that provides what we need, we can try to add another layer of indirection and combine the layers to produce the desired result.

Thanks to Jason Turner and Joshua Ogunyinka for bringing this topic up on twitter recently: