Какой из вариантов частичной специализации класса лучше C++
Я знаю, что есть несколько вариантов, но не понимаю, каким и когда нужно пользоваться.
Допустим, что я написал класс-обёртку для указателя, который магическим образом следит за вами как Большой Брат, чтобы вы не разыменовывали нулевой указатель, очищает за вами память после выхода из зоны видимости и т.д.
template <class Ty>
class super_puper_smart_pointer{
publuc:
super_puper_smart_pointer(Ty* ptr)
: _ptr(ptr) {
}
// ...
private:
Ty* _ptr;
};
И тут, вдруг, приходит осознание того, что вообще-то итераторы тоже было бы хорошо проверять. Например в таком коде
std::vector<int> m_numbers = {1, 2, 3};
auto first = std::find(v.begin(), v.end(), 0);
// assert(find != v.end);
if(*first == 0) {
// ...
}
закралась маленькая ошибка. Мы забыли проверить, что найденное значение это не конец последовательности.
Чтобы упростить жизнь, давайте напишем класс-обёртку для итераторов, который в конструкторе будет делать некоторые проверки.
Итак, подведя итоги, нам нужен класс-обёртка для указателей и для итераторов.
Чтобы можно было отличать одно от другого нам нужны:
- std::is_pointer<...>
- is_iterator<...>, реализация ниже
template <class It>
using iterator_category_t = typename ::std::iterator_traits<It>::iterator_category;
template <class Ty, class = void>
inline constexpr bool is_iterator_v = false;
template <class Ty>
inline constexpr bool is_iterator_v<Ty, ::std::void_t<iterator_category_t<Ty>>> = true;
Остался последний шаг - реализовать класс-обертку с помощью частичной специализации.
Вариант 1
// Реализация для указателя
template <class Ty, std::enable_if_t<std::is_pointer_v<Ty>> = nullptr>
class super_puper_smart_pointer{
publuc:
super_puper_smart_pointer(Ty ptr)
: _ptr(ptr) {
// какие-то проверки для указателя
}
template <class U>
friend class super_puper_smart_pointer;
private:
Ty _ptr;
};
// Реализация для итератора
template <class Ty, std::enable_if_t<is_iterator_v<Ty>> = nullptr>
class super_puper_smart_pointer{
publuc:
super_puper_smart_pointer(Ty it)
: _it(it) {
// какие-то проверки для итератора
}
template <class U>
friend class super_puper_smart_pointer;
private:
Ty _it;
};
Вариант 2
// Частичное объявление
template <class Ty, class = void>
class super_puper_smart_pointer;
// Реализация для указателя
template <class Ty>
class super_puper_smart_pointer<std::enable_if_t<std::is_pointer_v<Ty>, int>>{
publuc:
super_puper_smart_pointer(Ty ptr)
: _ptr(ptr) {
// какие-то проверки для указателя
}
template <class U>
friend class super_puper_smart_pointer;
private:
Ty _ptr;
};
// Реализация для итератора
template <class Ty>
class super_puper_smart_pointer<Ty, std::enable_if_t<is_iterator_v<Ty>, int>>{
publuc:
super_puper_smart_pointer(Ty it)
: _it(it) {
// какие-то проверки для итератора
}
template <class U>
friend class super_puper_smart_pointer;
private:
Ty _it;
};
Вопросы:
- Почему где-то нужно писать
std::enable_if_t<is_iterator_v<Ty>> = nullptr, а где-тоstd::enable_if_t<std::is_pointer_v<Ty>, int> - Как лучше писать?
- Научите, как правильно писать, потому что я даже в вопросе написал с ошибками, я не понимаю этот синтаксис
- Ещё то же самое можно сделать через void_t (по-моему), как?
- Когда писать
= nullptr - Когда писать
enable_if, а когдаenable_if_t
Ответы (1 шт):
Лучше не делать специализациями разные вещи. Так, vector<bool> часто приводят пример как ошибки, несмотря на то, что динамический битсет в принципе контейнер нужный.
Проверка итератора и проверка указателя -- разные проверки, то есть специализации будут слишком разные!
Проверки итераторов лучше не прикручивать извне, а делать на стороне реализации. Так они имеют больше доступа к деталям реализации.
Реализации STL обычно уже имеют все эти проверки в дебаг режиме. Лучший способ сделать свои такие проверки, если готовые не подходят -- сделать свои контейнеры STL.
std::enable_if_t<is_iterator_v<Ty>>* = nullptr и std::enable_if_t<std::is_pointer_v<Ty>, int> = 0 -- одно и тоже, вкусовщина. Это способ убить специализацию через SFINAE, если условие ложно
void_t -- это другое, это способ убить специализацию через SFINAE, если подстановка неуспешна. Например, так проверяют наличие метода.
enable_if, как и прочие не _t версии функций над типами -- более неудобные варианты, которые нужны были до появления using в контексте template typedef.