Reference:
https://eli.thegreenplace.net/2018/covariance-and-contravariance-in-subtyping
I've to say I admire Eli Bendersky a lot. Not only he could make complicated stuff in plain English, but also broadly read.
Covariant is a well known concept among expert C++ engineers, which includes the type system as well as the memory model. (Object memory layout.)
Even in 2018, I would recommand anyone who are into programming languages to give a quick read on Stanley B, Lippman's Inside The C++ Object Model.
Consider this code:
The reality is that function overrides are not covariant for pointer types.
They are invariant.
In fact, the vast majority of typing rules in C++ are invariant;
std::vector<Cat> is not a subclass of std::vector<Mammal>, even though Cat <: Mammal.
There's a good reason for that.
Let's look into the example from Java:
Thus, covariance together with mutability makes array types unsound.
Note, however, that this is not just a mistake - it's a deliberate historical decision made when Java didn't have generics and polymorphism was still desired; the same problem exists in C#.
Other languages have immutable containers, which can then be made covariant without jeopardizing the soundness of the type system.
Contravariant example in std::function:
https://eli.thegreenplace.net/2018/covariance-and-contravariance-in-subtyping
I've to say I admire Eli Bendersky a lot. Not only he could make complicated stuff in plain English, but also broadly read.
Covariant is a well known concept among expert C++ engineers, which includes the type system as well as the memory model. (Object memory layout.)
Even in 2018, I would recommand anyone who are into programming languages to give a quick read on Stanley B, Lippman's Inside The C++ Object Model.
Liskov substitution principle
Given types S and T with the relation S <: T, variance is a way to describe the relation between the composite types:
- Covariant means the ordering of component types is preserved: Composite<S> <:Composite<T>.
C++: works for function return type. - Contravariant means the ordering is reversed: Composite<T> <: Composite<S>.
C++: works for function parameter. i.e Argument type should be no deeper then function's parameter's signature type. - Bivariant means both covariant and contravariant.
- Invariant means neither covariant nor contravariant.
Consider this code:
struct MammalClinic {
virtual void Accept(Mammal* m);
};
struct CatClinic : public MammalClinic {
virtual void Accept(Cat* c);
};
The CatClinic::Accept is an overload, not override, and this is exactly the keyword: 'override' is created for: to spot this kind of error.The reality is that function overrides are not covariant for pointer types.
They are invariant.
In fact, the vast majority of typing rules in C++ are invariant;
std::vector<Cat> is not a subclass of std::vector<Mammal>, even though Cat <: Mammal.
There's a good reason for that.
Let's look into the example from Java:
class Main {
public static void main(String[] args) {
String strings[] = {"house", "daisy"};
Object objects[] = strings; // covariant
objects[1] = "cauliflower"; // works fine
objects[0] = 5; // throws exception at runtime.
}
}
Assigning an integer fails because at run-time it's known that objects is actually an array of strings.
Thus, covariance together with mutability makes array types unsound.
Note, however, that this is not just a mistake - it's a deliberate historical decision made when Java didn't have generics and polymorphism was still desired; the same problem exists in C#.
Other languages have immutable containers, which can then be made covariant without jeopardizing the soundness of the type system.
Contravariant example in std::function:
#include <functional>
struct Vertebrate {};
struct Mammal : public Vertebrate {};
struct Cat : public Mammal {};
Cat* f1(Vertebrate* v) {
return nullptr;
}
Vertebrate* f2(Vertebrate* v) {
return nullptr;
}
Cat* f3(Cat* v) {
return nullptr;
}
void User(std::function<Mammal*(Mammal*)> f) {
// do stuff with 'f'
}
int main() {
User(f1); // works
User(f2); // return type covariance failed, since Vertebrate is base type of Mammal.
User(f3); // Argument type contravariance failed, since Cat is deeper then Mammal.
return 0;
}

No comments:
Post a Comment
Note: Only a member of this blog may post a comment.