четверг, 20 ноября 2014 г.

[prog.c++] Давно так плотно не работал с шаблонами, интересные впечатления

Очень давно так плотно не погружался в 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 };

   ifthis->m_thread && !this->m_shutdown )
   {
      this->m_shutdown = true;
      this->notify();
   }
}

Посему лучше сразу все писать через this, и не заниматься правкой кода и борьбой с ошибками компиляции при рефакторинге. Но если все писать через this->, то тогда нужно отказываться от префикса m_, который за последние пятнадцать лет стал уже как родной :(

Еще несколько расстраивает многословность шаблонов при их специализации. Т.е. сейчас приходится писать:

templatetypename 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++, который с шаблонами и исключениями. Впрочем, это уже совсем другая тема для разговора.

Комментариев нет: