Шаблоны в C++
О том, что шаблоны вещь нужная и полезная распространяться много не буду, это и так ясно, с одной стороны... а с другой, в каких реальных задачах их необходимо применять, т.е. без них не обойтись? Мне пока хватало классического ООП без шаблонов. Под "применять" я имею ввиду самому разрабатывать, а не использовать готовые STL/boost.
А чем оно дорого? Автор кода на Nemerle за два дня его написал, по его словам. Потом ещё несколько раз подправлял. Несколько дней - не так уж дорого на код, который потом может использоваться годами.
Лично для меня полезность такого кода очевидна: ведь это здорово, когда компилятор сразу предупреждает об ошибках. Гораздо хуже, если для вылова таких ошибок приходится писать юнит-тесты и всячески мучиться.
Я являюсь поклонником статической типизации, и крайне негативно отношусь даже к тому, что какие-нибудь параметры задаются в виде строк, как например строки формата в операторах типа printf. Такие строки не проверяются компилятором (в Nemerle, впрочем, проверяются), что считаю плохо.
Имхо, механизм задания типам условий, разрешающих/запрещающих их участие в операциях с другими типами мог бы сильно уменьшить количество ошибок. Будь оно встроено изначально в язык, я бы активно пользовался. Уверен: пользовались бы многие. Не пользуются лишь потому, что этого пока нигде нет. Эти два примера с rsdn - первые ласточки.
Ну, логика какого-нибудь специализированного сильно-оптимизированного кода тоже не очевидна с ходу. Но зачем в нём разбираться? Им нужно пользоваться!
На введение доменов для пяти численных величин в трёх выражениях тратить два дня - вот я и говорю о подружке.
Правильность формул проверяется аналитиком с использованием соответствующих инструментов, а не компилятором. Модульное тестирование же необходимо в любом случае.
В Basic тоже проверяются. Макры Nemerle - да, весело, я ж не спорю. Тяжёлый случай порадовал возможностью генерировать винформы на C# в проектах Nemerle - и это действительно круто. Но конкретно данную задачу в рантайме я бы решил по паттерну Money, где вместо банка выступала бы система измерения. И никаких проблем: надо ускорение - просим ускорение. Умножаем на время - получаем скорость. Надо в системе СИ - сворачиваем выражение с коэффициентами из СИ. Это займёт минут 30.
Если у меня бешеные тыщи формул и мне нужно посчитать их в компайл-тайме - я посчитаю их в Maple и сгенерирую код.
Не люблю не понимать: разве хотя бы в C++, с его вездесущими неявными преобразованиями типов, есть правила, разрешающие любым типам участвовать в операциях с любыми другими типами?
Ну так какая тогда здесь показательная разница между шаблонами и макрами, если не смотреть в зубы реализации?
Ну почему в трёх выражениях? Ведь этот код теперь можно использовать в любых крупных проектах с тысячами выражений. И автоматически будет отслеживаться их правильность.
Зарплата аналитику - вот это дорого ;). Компилятор работает бесплатно ;).
Хорошо. С этим согласен. Но если для предметной области нет специализированного софта, наподобие Maple? Или он проприетарен и дорог?
Например, температура имеет тип double, масса имеет тип double. Компилятор позволит сложить эти величины, в то время как с физической точки зрения это не имеет смысла.
Я не о разнице между шаблонами и макрами, я о том, что этим написанным кодом уже можно пользоваться (хоть в C++, хоть в Nemerle), и это потенциально убережёт от некоторых ошибок.
В целом я говорю о том, что было бы удобно на любую переменную навешивать некоторый атрибут, оговаривающий взаимодействие этой переменной с любыми другими - что можно, что нельзя.
Вот и сомневаюсь: оправдан ли этот код популярностью задачи об абстрактной арифметике с произвольными величинами в компайл-тайме? Ну то есть обычно есть готовая матмодель, по которой строится реализация. Иными словами, формулы уже готовы, их не нужно проверять компилятором, а уж тем более выводить размерность в программе. Для символьной арифметики есть специализированные матпакеты и библиотеки. Зачем этот пугающий код?
Кроме того, для любого выражения здесь, кажется, должна быть величина, описывающая его размерность. Тогда "выносить за скобки" и прочая средства повышения выразительности (расчитывать заранее результаты вспомогательных выражений а-ля дискриминант в реквуре) применять будет затруднительно (поправьте, пожалуйста, если неправ).
Так даже если вы и программист и аналитик в одном лице, вы ж всё равно формулы на бумажке выводите, не так ли? Правильность размерности не говорит о правильности формул, так что результат всё равно надо тестировать - и проверочными формулами, и результатом.
Этот софт наверняка будет дешевле (не только в $-эквиваленте), чем дублирующий его функциональность метакод.
Эта задача решается обсуждаемым кодом так: для каждой величины вводится домен (тип), правильность контролируется совпадением типов. Однако домены тут неполноценны: нет и нельзя добавить контроль правильности значений, нельзя описать операции, специфичные для каждой конкретной величины. Я предложил просто завести домены ручками (+ домен, описывающий выражения), это снимет вышеописанные ограничения и займёт гораздо меньше времени - вот и всё. То есть будут просто конкретные типы Temperature и Mass и там, где нужны соответствующие величины, они и будут требоваться.
Так а я-то о том, что в обоих случаях код ужасен :)
"Маааленький" в виде кода не получится, тут надо на словах понимать. Например, есть у нас некий класс от стороннего произовдителя, реализующий работу COM-порта (или того же CAN). А мы хотим позволить пользователю работать с программой в режиме эмулятора, когда нужное железо не подключено. Делаем класс-заглушку, который имитирует работу нашего железа.
Наследоваться напрямую - неудобно и чревато. Во первых, придется переопределять все функции. Во вторых, в исходном классе могут остаться нежелательные обращения к железу в конструкторе и деструкторе, например.
Чтобы обеспечить полиморфизм, мы можем писать много писем, чтобы авторы отнаследовали свой класс от определённого абстрактного, а можем извернуться через шаблоны. При том, при использовании шаблонов можно заинкапсулировать все ненужные открытые методы.
Хотя, в общем, тут я пример преимуществ композиции более, чем шаблонов...
А можно примерчик? На любом языке, можно на псевдокоде. Или ссылку.
То есть сделать классы Temperature и Mass? Нет, меня такое не устраивает: это даст резкое снижение производительности, ведь понадобятся конструкторы копирования и прочее, то есть много работы будет в рантайме! Те два примера на rsdn делают проверки в компайл-тайме, а сами величины были и остаются простыми типами (int, double и т. п.)
Да хрен с ним, пусть ужасен. Может, Maple внутри тоже ужасен. Нам это не мешает его использовать в своих личных корыстных целях.
И, кстати, сдаётся мне, что на создание Maple было потрачено гораздо больше двух дней. Но пользователям-то это без разницы: главное, им теперь можно пользоваться.
Если бы все программеры только о девушках думали, то не было бы ни опен сорса, ни многого другого.
Допустим, британский учёный хочет просчитать развитие популяции карасей в пруду в зависимости от курса шекеля и фазы Луны. Есть для этого специализированный софт? Не думаю. А с помощью тех библиотек можно задать поведение любых простых типов. И уже нельзя будет приплюсовать карасей к малиновым крокодильчикам - компилятор не даст.
Видимо, я никак не могу объяснить то, чем меня так привлекла эта возможность.
Для чего придумали ООП? Чтобы можно было в программах использовать не биты/байты, не простые типа данных - целые и вещественные числа, - а сущности реального мира. Хочешь, класс Person создаёшь, описывающий человека, хочешь ещё что. Но этот путь имеет большой недостаток: размер кода получается большим, процессорного времени на работу с классами тратится много.
А тут я внезапно узрел то, о чём мог только мечтать: создаются внешне новые типы данных, с говорящими именами, с чётко заданным поведением, но на самом деле они остаются простыми числами. И вычисления с ними мгновенны.
К сожалению, прошло уже несколько лет, как появились те примеры на rsdn, а ими, похоже, никто особо не загорелся. Думаю, тут дело в инертности. Вот если нечто подобное будет введено в мэйнстримовый язык крупной корпорацией (скажем Microsoft в С++ и C#), то это вызовет волну восторгов: как мы раньше могли жить без этого, ведь это так удобно!
TypeProviders появятся в будущей версии F# (может Дон Сайм не знает о макросах?).
Интересные особенности:
1. Наиболее интересной мне показалась таблица расчёта типов (она идёт в самом начале). Она используется для вычисления типа формулы. Например, тип выражения расстояние/время должен быть типом скорости.
2. Второстепенной особенностью является автоматический расчёт коэффициентов перевода из одной системы в другую.
Замечания по использованию:
1. Думаю, область использования этой идеи довольно узка. Например, страшновато тут использовать величины, соотношения которых меняется хотя бы раз в год, ибо много ручной работы, требующей внимательности. Например, курсы валют так рассчитывать не будешь - велик риск запутаться.
2. Хорошо, если системы единиц можно пересчитать по формуле y = a*x. Если же формулы будут сложнее, даже y = a*x + b, то реализация намного усложнится или вообще будет невозможна.
3. Есть ещё мелочь, на которую не обращают герои - время компиляции (перекомпиляции). Если оно будет велико - то неудобно будет применять технологию маленьких шажков.
4. Ну и замечу, что это опять был пример применения шаблонах в библиотеках, которые меняются раз в сто лет. Это созвучно с пунктом 1.
Замечания к реализации.
1. Больше всего меня смущает функция pow в конструкторе. Как показывает практика, это сравнительно медленная функция (учитывая, что в степени может быть не целое число). А если определённый компилятор не сможет её рассчитать во время компиляции, то половина этих плясок будет зря.
2. Есть много однообразного кода. Но как решить это - не могу сказать. Хотя, можно было бы многое сократить простыми сишными макросами. Возможно, тут они уместны.
2. Есть много однообразного кода. Но как решить это - не могу сказать. Хотя, можно было бы многое сократить простыми сишными макросами. Возможно, тут они уместны.
Тут уместна машина вывода. Нашел таки не слишком давнее обсуждение подобной темы на RSDN. Ищите в топике упоминание Z3.
1. Статические ассерты.
template<> struct cCompileTimeError<true> {};
#define staticCheck( expr, msg ) \
{ cCompileTimeError< ((expr) != 0) > ERROR_##msg;(void)ERROR_##msg; }
Пример использования:
2. Характеристики (traits).
Примеры были приведены. Тут просто огромный простор для использования.
3. Автоматическое создание синглтонов.
static T object;
return object;
}
single<SomeClass>().doSomething();
...
single<SomeClass>().doSomethingelse();
Сколько бы мы не вызывали single<SomeClass>() будет возвращена ссылка на один и тот же объект. При этом он будет создан при первом вызовы.
Если недостаточно могу вспомнить еще.