Да, специализации одного и того же шаблона класса по умолчанию почти не имеют отношения и по существу обрабатываются как несвязанные типы.Но вы всегда можете определить неявные преобразования между типами классов, определив конструкторы преобразования (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 для проверки того, является ли определенная специализация шаблона, выражение, зависящее от типа, и т. Д. Допустимым или нет.)