Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Contravariance on abstract classes

I would like to create a nice interface on C++ on which each implementation needs to have the addition defined, on itself.

I would like to do something like this :

    class A{
        ...
        virtual A& operator+(const A& other) =0;
        ...
    }
    // this is my interface or abstract class.


    class B : A{
        ...
        B& operator+(const B& other);
        ...
    }
    // this is the kind of implementation i would like. a B element can be added by another B element only ! At least this the constraint I am aiming at.

as c++ does not accept contravariance, my function B& operator+(const B& other) does not implement virtual A& operator+(const A& other). Is there any tricky (but a little bit clean...) way to do that?

like image 986
Mathieu Bouyrie Avatar asked Nov 10 '14 15:11

Mathieu Bouyrie


People also ask

What is covariance and Contravariance in generics?

Covariance and contravariance are terms that refer to the ability to use a more derived type (more specific) or a less derived type (less specific) than originally specified. Generic type parameters support covariance and contravariance to provide greater flexibility in assigning and using generic types.

Can I inherit from an abstract class?

An abstract class cannot be inherited by structures. It can contain constructors or destructors.

What are the properties of abstract class?

Abstract classes have the following features: An abstract class cannot be instantiated. An abstract class may contain abstract methods and accessors. It is not possible to modify an abstract class with the sealed modifier because the two modifiers have opposite meanings.

What is the difference between covariance and Contravariance in C#?

In C#, covariance and contravariance enable implicit reference conversion for array types, delegate types, and generic type arguments. Covariance preserves assignment compatibility and contravariance reverses it.


2 Answers

template<class Y>
class A
{
    virtual Y& operator+=(const Y& other) = 0;
};

class B : A<B>
{
    // must implement B& operator+=(const B& other) else B is abstract
};

is one way. This idiom is common when implementing policies. See http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

like image 133
Bathsheba Avatar answered Oct 05 '22 08:10

Bathsheba


What you are trying to do is not possible in any language, because it does not make sense.

It is true that methods are contravariant in argument types in some languages, but that means that method is an overload if it accepts supertype. I.e. operator+(const A&) would be overload for operator+(const B&). But not the other way around. Because when you have two A instances (let's call them x and y) and write x + y, the method will be called and the compiler can't know whether both will be of the same subtype as that information will only be available at runtime. So at runtime is the only time you can check it.

So:

  1. If you do need an interface and will be using it polymorphically, then the operator must take the interface and check at runtime that it got compatible instance (but that does not inspire trust in the design).
  2. If you don't need to use it polymorphically, don't bother defining the interface at all. Just use + in the template that will be using it (it must be a template if it's not polymorphic) and the compiler will bitch when it's not defined. You may write a concept check class to find out early and avoid errors with too deep template expansion stack.
like image 29
Jan Hudec Avatar answered Oct 05 '22 07:10

Jan Hudec