My colleague and I are implementing Google Test for our code base and are running into a few issues with Contravariance regarding using standard template containers.
So, Google Test requires us to make a pure virtual Interface class that mirrors our actual class which will inherit the interface and implement all virtual functions. This is to be used in Google Mock for testing. This is a strict requirement to work too, otherwise we need to add templates to all of our classes which will only be of one type... this seems rather unintuitive just to get the test code working.
So we just investigated some code that exhibits the behavior of the issue:
#include <vector>
#include <string>
#include <iostream>
class Fruit{
public:
Fruit(std::string colorIn) : color(colorIn) {}
std::string color;
};
class Apple : public Fruit{
public:
Apple() : Fruit("Red"){ appleType = "Honey Crisp"; }
Apple(const Fruit& fruit) : Fruit(fruit.color) { appleType = "Honey Crisp"; }
std::string appleType;
};
class Banana : public Fruit{
public:
Banana() : Fruit("Yellow"){ bananaType = "Dole"; }
Banana(const Fruit& fruit) : Fruit(fruit.color) { bananaType = "Dole"; }
std::string bananaType;
};
void takeMyFruit(std::vector<Fruit>& fruits){
if(!fruits.empty()){
std::cout << "Take my " << fruits[0].color << " " << ((Banana)(fruits[0])).bananaType << " banana." << std::endl;
std::cout << "Take my " << fruits[1].color << " " << ((Apple)(fruits[1])).appleType << " apple." << std::endl;
}else{
std::cout << "You gave me an empty bag?" << std::endl;
}
}
int main(){
std::vector<Fruit> fruits;
fruits.push_back(Banana());
fruits.push_back(Apple());
std::vector<Banana> bananas = { Banana() };
std::vector<Apple> apples = { Apple() };
takeMyFruit(fruits); //Why can I do this?
//takeMyFruit(bananas); //Compile error due to contravariance
//takeMyFruit(apples); //Compile error due to contravariance
return 0;
}
We need to be able to compile something that can take a base type for a container, i.e. std::vector<BaseType>
, but we are only populating it with one single DerivedType
.
Why would we be allowed to mix two different derived types in the std::vector<Fruit>
in the above code example we created, (i.e. Apple
and Banana
), but not be able to pass a std::vector<DerivedType>
to a functional parameter that accepts std::vector<BaseType>
?
What would be the best way to get around this issue regarding Google Test and Google Mock. They say that if production code is being changed to suit the need for tests, then it probably isn't the best practice.
Another way to do this that we saw was to add templates for the derived types to any class that defines them as members. Doing this would be a rather large rehaul and would then require any user of the library we're creating to have to wrap every instantiation of these new classes that hold these interface/derived types just to get Google Mock to work.
We're operating on legacy code at this point that can't be changed this much to incorporate Google Mock. We can't just skip testing these new class types either, what would be the best method for moving forward?