Указание ковариантного / контравариантного поведения с помощью шаблонов C ++ - PullRequest
0 голосов
/ 19 мая 2018

Я смотрю на parboiled2 в Scala, и у него есть интересный способ кодирования поведения анализатора в системе типов с использованием ковариантных / контравариантных подтипов:

https://github.com/sirthias/parboiled2#rule-types-and-the-value-stack

Мне любопытноесли что-то подобное может быть достигнуто в C ++ с помощью шаблонного метапрограммирования.

Я подозреваю, что ковариантное поведение можно эмулировать с наследованием, но как быть с контравариантным?

1 Ответ

0 голосов
/ 19 мая 2018

В C ++, как и в других языках OO, подтипы обычно представлены наследованием.

Однако мы не можем моделировать отношения ковариации и контравариантности посредством наследования, потому что нет способа перечислить базовые классы класса(без каких-либо различных предложений по рефлексии, которые еще не проникли в язык).

Самый простой способ разрешить такой тип поведения - это позволить ковариантным и контравариантным шаблонным классам преобразовывать на основе отношениясвязанных типов.

Ковариация

Если Derived is-a Base, то Covariant<Derived> "should-be-a" Covariant<Base>.

Обычно решением было бы сделать так, чтобы Covariant<Derived> наследовал от Covariant<Base>, но в настоящее время у нас нет способа найти Base, учитывая только Derived.Однако мы можем включить преобразование, написав конструктор для Covariant<Base>, взяв любое Covariant<Derived>:

template <typename T>
struct Covariant {
    template <typename Derived>
    Covariant(const Covariant<Derived>& derived, 
              std::enable_if_t<std::is_base_of_v<T, Derived>>* = nullptr)
    {
        /* Do your conversion here */
    }
};

Контравариантность

Если Derived is-a Base, затем Contravariant<Base> "should-be-a" Contravariant<Derived>

Трюк здесь почти такой же - разрешить преобразование любого *От 1042 * до Contravariant<Derived>:

template <typename T>
struct Contravariant {
    template <typename Base>
    Contravariant(const Contravariant<Base>& base,
                  std::enable_if_t<std::is_base_of_v<Base, T>>* = nullptr)
    {
        /* Do your conversion here */
    }
};

Однако

У этого есть один существенный недостаток: вам нужно реализовать преобразования вручную и помнить, что случайное срезание объекта может разрушить вашу способность к обратному преобразованию (например, если вы определите ковариантный тип контейнера, который будет причиной больших головных болей).

По существу, пока рефлексия не позволит нам автоматизировать такого рода отношения наследования, преобразования будутЕдинственный способ сделать это, и я бы не рекомендовал использовать это для чего-то сложного.Как только вы сохраните объекты вашего T в ковариантном / контравариантном классах, вы попадете в мир боли.

Вот ссылка Godbolt , чтобы показать, чтоэто работает

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...