C++ Covariance in Parameters

C++ covariance in parameters

The return type is permissible since derived inherits from base, but the function parameter can't work - not all base instances will be a derived also. What's supposed to happen in the cases where func is called on a pointer to base with a parameter that's not a derived? The most derived implementation isn't callable.

C++ Covariant Parameters - Design Pattern

So you sound like you want to group operation implementations by model type. I'll explain a more OOP approach.

Separate Service from the implementations, but we're going to get rid of the pesky parameter:

class Service { ... };
class ServiceImpl {
virtual ~ServiceImpl() {}
void save() const = 0;
void remove() const = 0;
};

Each implementation will be lightweight and support the operations, but will take the parameter in the constructor:

class BoxService : public ServiceImpl {
Box _b;

public:
BoxService(Box b) : _b(b) {}

void save() const { ... }
void remove() const { ... }
};

Now we have an abstract factory to create implementations as we need them:

class ServiceImplFactory {
public:
std::unique_ptr<ServiceImpl> create(Model m) const {
if (auto b = dynamic_cast<Box*>(m)) {
return std::make_unique<BoxService>(*b);
} else if (auto x = dynamic_cast<X*>(m)) {
return std::make_unique<XService>(*x);
} else {
assert(!"Not all Models covered by Service implementations");
}
}
};

Now Service:

class Service {
ServiceImplFactory _implFactory;

public:
void save(Model m) const {
return _implFactory.create(m)->save();
}

void remove(Model m) const {
return _implFactory.create(m)->remove();
}
};

Further steps:

  • Engineer a solution that gives a compile-time error instead of asserting.
  • Allow both more model types and more operations to be added without changing (much) existing code. This should be equivalent to the Expression Problem. Notice that this approach of grouping operations by model type requires a much more widespread change to add a new operation than it does to add a new model type. The reverse would be true for using a visitor and grouping model types by operation. There are solutions to the Expression Problem, such as object algebras, but they can be a bit more obscure.

Why does C# use contravariance (not covariance) in input parameters with delegate?

Olivier's answer is correct; I thought I might try to explain this more intuitively.

Why does C# choose to use contravariance (not covariance) in input parameters in delegate?

Because contravariance is typesafe, covariance is not.

Instead of Base, let's say Mammal:

delegate void MammalDelegate(Mammal m);

This means "a function that takes a mammal and returns nothing".

So, suppose we have

void M(Giraffe x)

Can we use that as a mammal delegate? No. A mammal delegate must be able to accept any mammal, but M does not accept cats, it only accepts giraffes.

void N(Animal x)

Can we use that as a mammal delegate? Yes. A mammal delegate must be able to accept any mammal, and N does accept any mammal.

covariance/contravariance rule does not seem to work in this example.

There is no variance here to begin with. You are making the extremely common mistake of confusing assignment compatibility with covariance. Assignment compatibility is not covariance. Covariance is the property that a type system transformation preserves assignment compatibility.

Let me say that again.

You have a method that takes a Mammal. You can pass it a Giraffe. That is not covariance. That is assignment compatibility. The method has a formal parameter of type Mammal. That is a variable. You have a value of type Giraffe. That value can be assigned to that variable, so it is assignment compatible.

What then is variance, if it is not assignment compatibility? Let's look at an example or two:

A giraffe is assignment compatible with a variable of type mammal. Therefore a sequence of giraffes (IEnumerable<Giraffe>) is assignment compatible with a variable of type sequence of mammals (IEnumerable<Mammal>).

That is covariance. Covariance is the fact that we can deduce the assignment compatibility of two types from the assignment compatibility of two other types. We know that a giraffe may be assigned to a variable of type animal; that lets us deduce another assignment compatibility fact about two other types.

Your delegate example:

A mammal is assignment compatible with a variable of type animal. Therefore a method which takes an animal is assignment compatible with a variable of type delegate which takes a mammal.

That is contravariance. Contravariance is again, the fact that we can deduce the assignment compatibility of two things -- in this case a method can be assigned to a variable of a particular type -- from the assignment compatibility of two other types.

The difference between covariance and contravariance is simply that the "direction" is swapped. With covariance we know that A can be used as B implies that I<A> can be used as I<B>. With contravariance we know that I<B> can be used as I<A>.

Again: variance is a fact about the preservation of an assignment compatibility relationship across a transformation of types. It is not the fact that an instance of a subtype may be assigned to a variable of its supertype.

What other cases than delegate use covariance/contravariance and why?

  • Conversion of method groups to delegates uses covariance and contravariance on return and parameter types. This only works when the return / parameter types are reference types.

  • Generic delegates and interfaces may be marked as covariant or contravariant in their type parameters; the compiler will verify that the variance is always typesafe, and if not, will disallow the variance annotation. This only works when the type arguments are reference types.

  • Arrays where the element type is a reference type are covariant; this is not typesafe but it is legal. That is, you may use a Giraffe[] anywhere that an Animal[] is expected, even though you can put a turtle into an array of animals but not into an array of giraffes. Try to avoid doing that.

Note that C# does NOT support virtual function return type covariance. That is, you may not make a base class method virtual Animal M() and then in a derived class override Giraffe M(). C++ allows this, but C# does not.

UPDATE regarding the previous paragraph: This answer was written in 2016; in 2020, C# 9 does now support return type covariance.

Covariant generic parameter

Can someone explain the usage of the out T part with an example?

Sure. IEnumerable<T> is covariant. That means you can do this:

static void FeedAll(IEnumerable<Animal> animals) 
{
foreach(Animal animal in animals) animal.Feed();
}

...

IEnumerable<Giraffe> giraffes = GetABunchOfGiraffes();
FeedAll(giraffes);

"Covariant" means that the assignment compatibility relationship of the type argument is preserved in the generic type. Giraffe is assignment compatible with Animal, and therefore that relationship is preserved in the constructed types: IEnumerable<Giraffe> is assignment compatible with IEnumerable<Animal>.

Why is applicable only for interfaces and delegates and not for classes?

The problem with classes is that classes tend to have mutable fields. Let's take an example. Suppose we allowed this:

class C<out T>
{
private T t;

OK, now think this question through carefully before you go on. Can C<T> have any method outside of the constructor that sets the field t to something other than its default?

Because it must be typesafe, C<T> can now have no methods that take a T as an argument; T can only be returned. So who sets t, and where do they get the value they set it from?

Covariant class types really only work if the class is immutable. And we don't have a good way to make immutable classes in C#.

I wish we did, but we have to live with the CLR type system that we were given. I hope in the future we can have better support for both immutable classes, and for covariant classes.

If this feature interests you, consider reading my long series on how we designed and implemented the feature. Start from the bottom:

https://blogs.msdn.microsoft.com/ericlippert/tag/covariance-and-contravariance/

ref and out parameters in C# and cannot be marked as variant

"out" means, roughly speaking, "only appears in output positions".

"in" means, roughly speaking, "only appears in input positions".

The real story is a bit more complicated than that, but the keywords were chosen because most of the time this is the case.

Consider a method of an interface or the method represented by a delegate:

delegate void Foo</*???*/ T>(ref T item);

Does T appear in an input position? Yes. The caller can pass a value of T in via item; the callee Foo can read that. Therefore T cannot be marked "out".

Does T appear in an output position? Yes. The callee can write a new value to item, which the caller can then read. Therefore T cannot be marked "in".

Therefore if T appears in a "ref" formal parameter, T cannot be marked as either in or out.

Let's look at some real examples of how things go wrong. Suppose this were legal:

delegate void X<out T>(ref T item);
...
X<Dog> x1 = (ref Dog d)=>{ d.Bark(); }
X<Animal> x2 = x1; // covariant;
Animal a = new Cat();
x2(ref a);

Well dog my cats, we just made a cat bark. "out" cannot be legal.

What about "in"?

delegate void X<in T>(ref T item);
...
X<Animal> x1 = (ref Animal a)=>{ a = new Cat(); }
X<Dog> x2 = x1; // contravariant;
Dog d = new Dog();
x2(ref d);

And we just put a cat in a variable that can only hold dogs. T cannot be marked "in" either.

What about an out parameter?

delegate void Foo</*???*/T>(out T item);

? Now T only appears in an output position. Should it be legal to make T marked as "out"?

Unfortunately no. "out" actually is not different than "ref" behind the scenes. The only difference between "out" and "ref" is that the compiler forbids reading from an out parameter before it is assigned by the callee, and that the compiler requires assignment before the callee returns normally. Someone who wrote an implementation of this interface in a .NET language other than C# would be able to read from the item before it was initialized, and therefore it could be used as an input. We therefore forbid marking T as "out" in this case. That's regrettable, but nothing we can do about it; we have to obey the type safety rules of the CLR.

Furthermore, the rule of "out" parameters is that they cannot be used for input before they are written to. There is no rule that they cannot be used for input after they are written to. Suppose we allowed

delegate void X<out T>(out T item);
class C
{
Animal a;
void M()
{
X<Dog> x1 = (out Dog d) =>
{
d = null;
N();
if (d != null)
d.Bark();
};
x<Animal> x2 = x1; // Suppose this were legal covariance.
x2(out this.a);
}
void N()
{
if (this.a == null)
this.a = new Cat();
}
}

Once more we have made a cat bark. We cannot allow T to be "out".

It is very foolish to use out parameters for input in this way, but legal.


UPDATE: C# 7 has added in as a formal parameter declaration, which means that we now have both in and out meaning two things; this is going to create some confusion. Let me clear that up:

  • in, out and ref on a formal parameter declaration in a parameter list means "this parameter is an alias to a variable supplied by the caller".
  • ref means "the callee may read or write the aliased variable, and it must be known to be assigned before the call.
  • out means "the callee must write the aliased variable via the alias before it returns normally". It also means that the callee must not read the aliased variable via the alias before it writes it, because the variable might not be definitely assigned.
  • in means "the callee may read the aliased variable but does not write to it via the alias". The purpose of in is to solve a rare performance problem, whereby a large struct must be passed "by value" but it is expensive to do so. As an implementation detail, in parameters are typically passed via a pointer-sized value, which is faster than copying by value, but slower on the dereference.
  • From the CLR's perspective, in, out and ref are all the same thing; the rules about who reads and writes what variables at what times, the CLR does not know or care.
  • Since it is the CLR that enforces rules about variance, rules that apply to ref also apply to in and out parameters.

In contrast, in and out on type parameter declarations mean "this type parameter must not be used in a covariant manner" and "this type parameter must not be used in a contravariant manner", respectively.

As noted above, we chose in and out for those modifiers because if we see IFoo<in T, out U> then T is used in "input" positions and U is used in "output" positions. Though that is not strictly true, it is true enough in the 99.9% use case that it is a helpful mnemonic.

It is unfortunate that interface IFoo<in T, out U> { void Foo(in T t, out U u); } is illegal because it looks like it ought to work. It cannot work because from the CLR verifier's perspective, those are both ref parameters and therefore read-write.

This is just one of those weird, unintended situations where two features that logically ought to work together do not work well together for implementation detail reasons.

Is covariance all about accepting values?

As Josh pointed out the book is correct.

You can check this link if you want to confirm it from another source.

IEnumerable<Cat> is a subtype of IEnumerable<Animal>. The subtyping is preserved because IEnumerable<T> is covariant on T.

The two keywords in C# for those concepts are out for covariant and in for contravariant. In the IEnumerable<T> case this translate to IEnumerable<out T>.

Hope this helps.

UPDATE

You would have to inverse your definitions as follows.

Covariant Meaning that the generic type parameter can be a certain class and all the derived classes from it (IEnumerable<Object> can be a IEnumerable<String> since String is a subtype of Object). In C#, you indicate covariant generic type parameters with the out keyword.

Contravariant Meaning that the generic type argument can change from a
class to one of its base classes. In C#, you indicate contravariant
generic type parameters with the in keyword.

why covariant type parameters are used only for the return types of the members?

Why do covariant type parameters like IEnumerable<out T> have type T used only for the return type?

First off: T does not have to be used only for the return type. For example:

interface I1<out T>{ T M1(); }
interface I2<out T>{ void M2(Action<I1<T>> a); }

In I1<T>, T is used only in return type positions. But in I2, T is used in an input a, and an I1<T> is the input of the Action, so in a sense it is being used in two input positions here.

But let's consider just a simpler case. Why can we make I1 covariant in T but not contravariant in T?

The reason is because covariance is safe and contravariance is not. We can see that covariance is safe:

class Animal {}
class Mammal : Animal {}
class Tiger : Mammal {}
class Giraffe : Mammal {}
class C : I1<Mammal> {
public Mammal M1() { return new Tiger(); }
}
I1<Mammal> i1m = new C(); // Legal
I1<Animal> i1a = i1m; // Legal
Animal a = i1a.M1(); // Returns a tiger; assigned to animal, good!

No matter what C.M1 returns, it is always a Mammal and therefore always an Animal.

But this cannot be legal:

I1<Giraffe> i1g = i1m; // Not legal
Giraffe g = i1g.M1(); // Returns a tiger; assigned to giraffe, bad!

The first line has to be illegal so that the second line never executes.

Now you should have enough information to figure out why contravariance works the way it does. Remember, you can always come back to a simple example and ask yourself "if this was legal, what mistakes could I make later?" The type system is protecting you from making those mistakes!

Exercise: Do this same analysis of I2<T>. Do you see why it is legal to use T in two input positions even though it is out. (Hint: Action is contravariant, so it reverses the direction of assignment compatibility. What happens if you reverse directions twice?)

still confused about covariance and contravariance & in/out

Both covariance and contravariance in C# 4.0 refer to the ability of using a derived class instead of base class. The in/out keywords are compiler hints to indicate whether or not the type parameters will be used for input and output.

Covariance

Covariance in C# 4.0 is aided by out keyword and it means that a generic type using a derived class of the out type parameter is OK. Hence

IEnumerable<Fruit> fruit = new List<Apple>();

Since Apple is a Fruit, List<Apple> can be safely used as IEnumerable<Fruit>

Contravariance

Contravariance is the in keyword and it denotes input types, usually in delegates. The principle is the same, it means that the delegate can accept more derived class.

public delegate void Func<in T>(T param);

This means that if we have a Func<Fruit>, it can be converted to Func<Apple>.

Func<Fruit> fruitFunc = (fruit)=>{};
Func<Apple> appleFunc = fruitFunc;

Why are they called co/contravariance if they are basically the same thing?

Because even though the principle is the same, safe casting from derived to base, when used on the input types, we can safely cast a less derived type (Func<Fruit>) to a more derived type (Func<Apple>), which makes sense, since any function that takes Fruit, can also take Apple.

What violations does using a covariant type in a contravariant position here enable?

You:

interface ICov<out T>    // BAD!
{
void maybe_safe_set(T v);
}

Here comes the problem. As usual we have:

class Animal { /* ... */ }
class Dog : Animal { public void Woof() { } /* ... */ }
class Cat : Animal { /* ... */ }

Then consider:

class Impl : ICov<Dog>
{
public void maybe_safe_set(Dog v)
{
v.Woof(); // our 'Dog' v can really bark
}
}

which will compile just fine.

Then this:

var impl1 = new Impl();
ICov<Dog> impl2 = impl1; // OK, implements that
ICov<Animal> impl3 = impl2; // OK, you claim interface is covariant ('out')!

var badAnimal = new Cat();
impl3.maybe_safe_set(badAnimal); // ICov<Animal> takes in Animal, right?

// oh my God, you mad a 'Cat' bark!

It is always the same example when people ask about co- and contravariance.

C# dont understand covariance and contravariance of delegates

Following the comments under the question, the Variance in Delegates document will explain that

.NET Framework 3.5 introduced variance support for matching method
signatures with delegate types in all delegates in C#. This means that
you can assign to delegates not only methods that have matching
signatures, but also methods that return more derived types
(covariance) or that accept parameters that have less derived types
(contravariance) than that specified by the delegate type.

So, your assignment MyDelegate<Dog, Mammal> myDelegate = TestMethod; is perfectly fine, despite different signatures on delegate and TestMethod (reversed input parameter and return type).

But in and out parameters are still needed when you have an implicit conversion between delegates, according to Variance in Generic Type Parameters section

To enable implicit conversion, you must explicitly declare generic
parameters in a delegate as covariant or contravariant by using the in
or out keyword.

E.g. the following code will not compile

MyDelegate<Dog, Mammal> myDelegate = TestMethod;
MyDelegate<Dog, Dog> anotherDelegate = TestMethod;
myDelegate = anotherDelegate; //error CS0029: Cannot implicitly convert type...

Until you declare MyDelegate witn contravariant parameter and covariant return type

delegate TOut MyDelegate<in TIn, out TOut>(TIn input);

After that last line will compile



Related Topics



Leave a reply



Submit