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