Как `shared_ptr`s достичь ковариации? - PullRequest
4 голосов
/ 10 марта 2019

Можно скопировать или построить shared_ptr<Base> из shared_ptr<Deriver> (т.е. shared_ptr<Base> ptr = make_shared<Derived>()). Но, как мы все знаем, классы шаблонов не могут быть преобразованы друг в друга, даже если аргументы шаблона есть. Так как же shared_ptr s может проверить, являются ли значения их указателей конвертируемыми, и выполнить преобразование, если они есть?

Ответы [ 2 ]

4 голосов
/ 10 марта 2019

Да, специализации одного и того же шаблона класса по умолчанию почти не имеют отношения и по существу обрабатываются как несвязанные типы.Но вы всегда можете определить неявные преобразования между типами классов, определив конструкторы преобразования (To::To(const From&)) и / или функции преобразования (From::operator To() const).

Так что std::shared_ptr определяет конструкторы преобразования шаблонов:

namespace std {
    template <class T>
    class shared_ptr {
    public:
        template <class Y>
        shared_ptr(const shared_ptr<Y>&);
        template <class Y>
        shared_ptr(shared_ptr<Y>&&);
        // ...
    };
}

Хотя объявление, как показано, разрешает преобразования из любого shared_ptr в любой другой, а не только тогда, когда типы аргументов шаблона совместимы.Но в стандарте также говорится об этих конструкторах ( [util.smartptr] / 5 и [util.smartptr.const] / 18 и util.smartptr.const] / 21):

Для целей подпункта [util.smartptr] тип указателя Y* называется совместимым с типом указателя T*, когдаY* может быть преобразовано в T* или Y - U[N], а T - cv U[].

Конструктор [...] не должен участвоватьв разрешении перегрузки, если Y* не совместимо с T*.

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

#include <cstddef>
#include <type_traits>

namespace std {
    template <class Y, class T>
    struct __smartptr_compatible
        : is_convertible<Y*, T*> {};

    template <class U, class V, size_t N>
    struct __smartptr_compatible<U[N], V[]>
        : bool_constant<is_same_v<remove_cv_t<U>, remove_cv_t<V>> &&
                        is_convertible_v<U*, V*>> {};

    template <class T>
    class shared_ptr {
    public:
        template <class Y, class = enable_if_t<__smartptr_compatible<Y, T>::value>>
        shared_ptr(const shared_ptr<Y>&);

        template <class Y, class = enable_if_t<__smartptr_compatible<Y, T>::value>>
        shared_ptr(shared_ptr<Y>&&);

        // ...
    };
}

Здесь вспомогательный шаблон __smartptr_compatible<Y, T> действует как «черта»: он имеет static constexpr член value, который равен true, когда типы совместимы, как определено,или false в противном случае.Тогда std::enable_if является признаком, который имеет тип члена, называемый type, когда его первый аргумент шаблона равен true, или не имеет члена с именем type, когда его первый аргумент шаблона равен false, что делает псевдоним типаstd::enable_if_t недопустимо.

Так что если вычет типа шаблона для любого из конструкторов выводит тип Y, так что Y* не совместим с T*, подставляя Y в шаблон enable_if_t по умолчаниюАргумент неверен.Поскольку это происходит при замене выведенного аргумента шаблона, в результате просто удаляется весь шаблон функции из рассмотрения для разрешения перегрузки.Иногда метод SFINAE используется для принудительного выбора другой перегрузки вместо этого, или, как здесь (в большинстве случаев), он может просто заставить код пользователя не скомпилироваться.Хотя в случае ошибки компиляции, это поможет, если где-то в выводе появится сообщение о том, что шаблон был недействительным, а не какая-то ошибка даже глубже во внутреннем коде шаблона.(Кроме того, подобная настройка SFINAE позволяет другому шаблону использовать собственную технику SFINAE для проверки того, является ли определенная специализация шаблона, выражение, зависящее от типа, и т. Д. Допустимым или нет.)

0 голосов
/ 10 марта 2019

Это работает, потому что shared_ptr имеет (среди прочего) шаблонный конструктор

template<typename U> shared_ptr(U * ptr);

Если U * не конвертируется в содержащийся тип shared_ptr, вы получите ошибку, скрытую где-то в реализации shared_ptr.

...