Очень давно так плотно не погружался в C++ные шаблоны, как в последние несколько дней. Вроде как ничего сложного, и нет такого, чтобы шаблон на шаблоне шаблоном погоняет. Тем не менее, что-то вроде вот такого:
template< typename ENGINE, typename CONSUMER > class basic_methods_impl_mixin : protected mixin_selector< typename ENGINE::thread_safety, CONSUMER >::type , public ENGINE::defaults_type { |
и вот такого:
template< typename THREAD_SAFETY,
typename ERROR_LOGGER,
typename ACTOR_EXCEPTION_HANDLER > class timer_heap_manager_template : public details::manager_impl_template< details::timer_heap_engine< THREAD_SAFETY, ERROR_LOGGER, ACTOR_EXCEPTION_HANDLER > > { |
встречается регулярно :)
Проверяя написанное неоднократно ловил себя на мысли, что программирование на C++ных шаблонах -- это как программирование на динамически-типизированном языке вроде Ruby. Т.е. если не напишешь unit-тесты на все куски шаблонного класса, ничего о корректности своего кода сказать не можешь. Причем, если в коде нет реального инстанциирования шаблона, то некоторые компиляторы (не будем лишний раз тыкать палочкой в VC++, но...) пропускают чуть ли не синтаксические ошибки в коде :)
Еще одно впечатление: похоже, при написании кода шаблона самый лучший способ обращения к атрибутам и методам шаблонного класса -- это через this->. Т.е. во многих случаях можно писать так:
void shutdown() { typename base_type::lock_guard locker{ *this }; if( m_thread && !m_shutdown ) { m_shutdown = true; notify(); } } |
Но, если часть атрибутов/методов со временем переезжает в базовый шаблонный класс, то для обращения к ним уже нужно использовать this:
void shutdown() { typename base_type::lock_guard locker{ *this }; if( this->m_thread && !this->m_shutdown ) { this->m_shutdown = true; this->notify(); } } |
Посему лучше сразу все писать через this, и не заниматься правкой кода и борьбой с ошибками компиляции при рефакторинге. Но если все писать через this->, то тогда нужно отказываться от префикса m_, который за последние пятнадцать лет стал уже как родной :(
Еще несколько расстраивает многословность шаблонов при их специализации. Т.е. сейчас приходится писать:
template< typename THREAD_SAFETY, typename CONSUMER > struct mixin_selector { // Специально пустой, т.к. без наличия конкретной специализации // должна возникать ошибка компиляции. }; template<> struct mixin_selector< thread_safety::unsafe, consumer_type::manager > { using type = thread_unsafe_manager_mixin; }; template<> struct mixin_selector< thread_safety::safe, consumer_type::manager > { using type = thread_safe_manager_mixin; }; template<> struct mixin_selector< thread_safety::safe, consumer_type::thread > { using type = thread_mixin; }; |
Тогда как хотелось бы обойтись без лишних пустых деклараций. Что-то вроде:
template for<thread_safety::unsafe, consumer_type::manager> struct mixin_selector { using type = thread_unsafe_manager_mixin; }; template for<thread_safety::safe, consumer_type::manager> struct mixin_selector { using type = thread_safe_manager_mixin; }; template for<thread_safety::safe, consumer_type::thread> struct mixin_selector { using type = thread_mixin; }; |
Только вот не уверен, насколько это возможно. И видит ли в этом недостаток еще кто-нибудь кроме меня :)
Но вообще, конечно, C++ные шаблоны -- это очень мощная штука. Когда в зависимости от параметра шаблона в объекте кардинальным образом меняется набор атрибутов -- это внушает. Причем, за всем этим следит компилятор и бьет по рукам при попытке обратиться к отсутствующему атрибуту или же если этот атрибут имеет другой тип.
Лично мне в этой связи часто вспоминается появление Java. Сейчас мало кто помнит, но шаблоны появились в C++ в конце 1980-х, самом начале 1990-х (при том, что официальной датой рождения C++ считается 1986-й год). Конечно, массовой поддержки шаблонов в C++ных компиляторах не было, да и там, где она была, были сильные различия в качестве ее реализации. Однако факты именно таковы: Степанов в 1993-м уже демонстрировал миру свои наработки по STL на базе C++ных шаблонов. Тогда как сама Java в Sun-е в это время существовала в виде наработок в рамках Green- и Oak-проектов.
А решение о выпуске ее в свет, да еще под именем Java, было принято в 1994-м. После чего началась рекламно-пропагандистская компания по подготовке мира к принятию Java. Сколько тогда было многообещающих статей о том, что Sun выкинул из C++ все самое плохое, оставил все самое лучшее, да еще и добавил сборщик мусора и реальную бинарную кроссплатформенность. А что получилось по факту? Как по мне, как сильно кастрированное и унылое говно. Гослинг со товарищи правильно сделали, что выбросили препроцессор и совместимость с C. Но вот то, что они не захотели сделать в Java шаблоны, да еще тогда, когда уже было видно, что шаблоны -- это одна из самых мощных возможностей C++... Иначе как ниасиляторством обозвать это нельзя.
Собственно, я вполне хорошо понимаю людей, которые набили себе шишек на "C with classes" и первых версиях Java (а то и C#) и ушли в OCaml-ы с Haskell-ем. Но, сдается мне, далеко не всегда у них были хорошие знания нормального C++, который с шаблонами и исключениями. Впрочем, это уже совсем другая тема для разговора.
Комментариев нет:
Отправить комментарий