Ваш вопрос касается ковариации и контравариантности функций, и я думаю, что это поможет вам понять, если мы сопоставим некоторые не зависящие от языка символические отношения в статье с реальным кодом.В C ++ обсуждаемая здесь функция будет выглядеть так:
int GetSpeedOf(Vehicle vehicle);
Подтип должен пониматься в терминах Лисковская замена ;если функция ожидает какого-либо типа животного, вы можете дать ему кошку, и все должно работать нормально, но это не так;функция, требующая кота, не может работать на любом типе животных.
Предполагая, что вы понимаете, что можно передать Car в функцию GetSpeedOf, теперь мы рассмотрим более сложный случай принятия функций функциями, которыевносит противоречивость в картину.
У следующего CarWrapper есть личный автомобиль, с которым он будет делать вещи, используя функцию, предоставляемую извне.Эта функция должна работать для автомобилей.Так что, если вы дадите функцию, которая работает более широко для всех транспортных средств, это нормально.
#include <functional>
class Vehicle { };
class Car : public Vehicle { };
class CarWrapper
{
Car car;
typedef std::function<auto(Car) -> void> CarCallback;
CarCallback functionPtr;
public:
void PassCallback(CarCallback cb)
{
functionPtr = cb;
}
void DoStuff()
{
functionPtr(car);
}
};
void Works(Vehicle v){}
int main() {
CarWrapper b;
b.PassCallback(&Works);
}
Итак, функция PassCallback ожидает тип CarCallback ("Car -> void" в вашей статье), но мы даемэто подтип «Vehicle -> void», потому что тип «& Works» на самом деле std::function<auto(Vehicle) -> void>
.Следовательно, «если функция принимает Автомобиль, то это подтип функции, которая принимает Автомобиль».Это возможно, потому что все операции, которые будет выполнять функция «Works», также должны быть возможны для того, что фактически передается в автомобиле.
Вариант использования этого заключается в том, что функция Works можеттакже будет передан в класс BoatWrapper, который ожидает функцию, работающую на лодках.Мы можем дать подтип этого типа функции «Vehicle -> void», зная, что все операции в фактической переданной функции также должны быть доступны на переданной ей лодке, поскольку Boat является подтипом Vehicle, а наша фактическая функция использует толькочем более общий параметр Vehicle для параметра.
Ковариантность, с другой стороны, влияет на тип возвращаемого значения;если бы класс CarWrapper ожидал обратного вызова, который генерирует для нас Car, мы не смогли бы передать функцию создания транспортного средства, потому что тогда CarWrapper не смог бы использовать результат для конкретных автомобилей.
Если бы у нас была функция, ожидающая генератор транспортного средства, мы могли бы предоставить ему генератор автомобилей или генератор лодок;следовательно (void -> Car) является подтипом (void -> Vehicle), если Car является подтипом Vehicle.
Ковариация подразумевает, что отношение подтипа остается в том же направлении, поэтому мы можем продолжать придерживаться в другом приложении функции, и все же «Сторона автомобиля» будет подтипом «Стороны автомобиля», то есть:
Car is a subtype of Vehicle means that:
(void -> Car) is a subtype of (void -> Vehicle) - as in the code sample above
(void -> (void -> Car)) is a subtype of (void -> (void -> Vehicle))
(void -> (void -> (void -> Car))) is a subtype of (void -> (void -> (void -> Vehicle)))
Это означает, что, если мы ожидали, что VehicleFactoryFactoryFactory будет удовлетворен, если нам будет предоставлен CarFactoryFactoryFactory:
#include <functional>
class Vehicle { };
class Car : public Vehicle { };
typedef std::function<auto() -> Vehicle> VehicleFactory;
typedef std::function<auto() -> VehicleFactory> VehicleFactoryFactory;
typedef std::function<auto() -> VehicleFactoryFactory> VehicleFactoryFactoryFactory;
void GiveMeAFactory(VehicleFactoryFactoryFactory factory)
{
Vehicle theVehicle = factory()()();
}
typedef std::function<auto() -> Car> CarFactory;
typedef std::function<auto() -> CarFactory> CarFactoryFactory;
typedef std::function<auto() -> CarFactoryFactory> CarFactoryFactoryFactory;
Car ActualCarCreateFunc() { return Car(); }
CarFactory CarFactoryCreateFunc() { return &ActualCarCreateFunc; }
CarFactoryFactory CarFactoryFactoryCreateFunc() { return &CarFactoryCreateFunc; }
int main() {
GiveMeAFactory(&CarFactoryFactoryCreateFunc);
}
При несовпадении типов параметров отношение инвертируется с каждым приложением функции.
Car is a subtype of Vehicle means that:
(Vehicle -> void) is a subtype of (Car -> void)
((Car -> void) -> void) is a subtype of ((Vehicle -> void) -> void)
(((Vehicle -> void) -> void) -> void) is a subtype of (((Car -> void) -> void) -> void)
В случае контравариантности очень трудно понять это в интуитивных терминах.Вот почему мой CarWrapper пытается объяснить его только для одного применения правила, в то время как пример CarFactory содержит три применения ковариации.