Комментарии 59
Блин, он всё страшнее и страшнее с каждой версией :))
Самому жутко нравится. Постоянно ставлю себя на место новичков и смотрю на свой код.
Понимаю, что ничего не понимаю, хотя и стараюсь писать в общепринятых рамках
"короче" не значит "лучше". Последнее время много приходится работать с Go. Там совсем не короче все. Зато даже бегло зная язык прям все понятно, даже не вчитываясь. С "новым" С++ такого не достичь, даже с большим опытом работы с языком. Особенно если в команду затесываются любители эксперементировать. Есть опыт когда код просто стал неподдерживаемым. И человек который его написал сам решил что переписать легче чем править и дополнять.
Лучше не так.
Сколько строк займет написание простой и понятной альтернативы этому коду?
template < typename... Func > class Visitor : Func... { using Func::operator()...; }
template < class... Func > make_visitor(Func...) -> Visitor < Func... >;
п.с. есть проползал на std::overload, как раз для этих целей.
Это значит, что в новом стандарте конструкции выглядят хтонически.
Например каких языков?
А ну, альтернативы?
Что там в 2017, уже все пишут десктопные приложения на Go, или, может, Mozilla переехала на Servo, написанный на Rust? Есть монструозная Java, есть привязанные к своим платформам C# и Swift. Причём, первые два ещё и требуют докачивать среду выполнения и устанавливать её.
Все эти языки хороши, если играться ими на уютном сервере, а в случае с приложениями юзер выберет то, что легче и проще в установке.
А на деле оказывается, что все БД (PostgreSQL, MySQL, SQLite, MongoDB, Redis, etc) написаны на C/C++, все популярные серверы написаны на C/C++ (nginx, apache, lighttpd, etc), все популярные браузеры написаны на C/C++ (лиса, хром, опера, etc), многие веб-движки и фреймворки написаны на C/C++ (memcached, V8, NodeJS, etc), многие частоиспользуемые нетривиальные сервисы написаны на C/C++ (инфраструктура Google, Яндекса, Facebook, etc).
Но язык, конечно, пора хоронить, потому что сайты-визитки на нем делать неудобно. Ну-ну.
Нужно различать фичи, которые добавлены для авторов библиотек от фич для прикладных программистов. Например благодаря auto
на C++ стало поудобнее писать, чем на Java/С#.
Текст этой ошибки не удастся разобрать, даже если вы используете очень простой код
чтобы избежать таких вот проблем, необходимо проверять входные значения через std::enable_if. А это ОЧЕНЬ много кода
Да, поэтому я и упомянул Concepts TS, который можно потрогать в последних gcc. Ну и какую — никакую реализацию можно глянуть в репозитории, ссылка на который в конце статьи
Кстати, а в каких заголовочных файлах определены library concepts?
shapes.emplace_back(EquilateralTriangle { 5.6 });
shapes.emplace_back(Square { 8.2 });
Не понял. vector — это std::vector? Это же не скомпилируется. Где тут variant?
Вот как можно сделать НАСТОЯЩИЕ гетерогенные контейнеры: https://gieseanw.wordpress.com/2017/05/03/a-true-heterogeneous-container-in-c/
В статье, в принципе, пошли по пути создания собственного контейнера-адаптера с запоминанием складываемых типов :). От того же класса — визитора для последующей работы с ним никуда не ушли)
Здесь же лёгкие функции для более удобной работы с std::variant, который упрятан в любой неассоциативный контейнер.
Что касается visitor, по-моему, по другому обойти гетерогенный контейнер не получится, так что в любой реализации будет visitor
А в boost недавно приняли библиотеку poly_collection.
Пишу на С++ с 90х, и с каждой версией все больше и больше нравится простой С, а сейчас — Go. Потому что там нельзя написать что-то вроде
template < typename... Func > class Visitor : Func... { using Func::operator()...; }
template < class... Func > make_visitor(Func...) -> Visitor < Func... >;
Потому что это write-only код. Его нельзя пробежать глазами и сразу понять что он делает. Даже если его сам написал месяц назад. Единственный плюс такого кода — почесать себе эго тем что красиво что-то написал и сэкономил десяток строк. Код должен быть для людей. Компилятору пофиг что компилировать ведь.
P.S. несмотря на мои кряхтения, статья хорошо описывает std::variant
и с чем его кушать. Спасибо.
У меня были такие же мысли, когда было мало практики с variadic packs. Нужно побольше попрактиковаться, понять концепции, которые двигали авторов при создании такого синтаксиса и всё становится намного легче, в частности и с вышеприведенным кодом :)
Но не могу не согласиться, что всё это далеко от совершенства, особенно когда начинаешь навешивать форвардинг ссылок, sfinae защиту и т.п. В особо страшныхсложных случаях получившуюся кашу даже страшно потом открывать
Это окупается удобством использования библиотек в прикладных программах.
Go какбы ни разу не альтернатива С++ из-за сборщика мусора и вот этого всего.
Как известно, настоящих гетерогенных контейнеров, работающих в рантайме на c++ нет...
Я как раз разбирал один способ создания неоднородных контейнеров.
Ну и выше тоже давали ссылку.
Далее, по самой статье. Вы как-то быстро перешли к мелким техническим деталям, совершенно не пояснив зачем это вообще нужно. Т.е. из вашей статьи совсем не очевидно когда надо применять полиморфизм на виртуальных функциях, а когда указанный подход. Лично я начал бы подобную статью с перечисления возможных вариантов реализации подобных динамических гетерогенных коллекций (например виртуальные функции, variant, any) и обсуждениях их плюсов и минусов. Типа такого:
— виртуальные функции: реализуются через ссылочное размещение (минус — ухудшение производительности из-за лишней косвенности), требуют правки хранимых типов данных в виде добавления общего наследника (минус — нельзя сделать коллекцию из int, double, string), не ограничивают число хранимых типов (плюс — можно бесконечно расширяться за счёт новых классов-потомков).
— variant: реализуются через размещение по месту (плюс — нет ухудшение производительности), не требуют правки хранимых типов данных (плюс — можно сделать коллекцию из int, double, string), ограничивают число хранимых типов (минус — нельзя добавить новые хранимые типы без модификации кода контейнера).
— any: реализуются через ссылочное размещение (минус — ухудшение производительности из-за лишней косвенности), не требуют правки хранимых типов данных (плюс — можно сделать коллекцию из int, double, string), не ограничивают число хранимых типов (плюс — можно расширяться на любые хранимые типы).
А т.к. в вашей статье в качестве примера приводится как раз случай реализуемый с помощью виртуальных функций (в контейнере нет например базовых типов) и нет тестов производительности, то из неё совершенно непонятно в чём вообще смысл применения подобных решений. Единственная попытка сравнения свелась к весьма субъективному понятию красоты кода, причём в данном конкретном случае ещё и не вполне однозначному (для данного конкретного случая вариант на полиморфных функциях будет покрасивее, если записать всё корректно).
Ну а в остальном статья конечно же очень хорошая и чувствуется, что автор хорошо знаком с особенностями метапрограммирования на шаблонах.
Огромное спасибо за подробный комментарий :)
По началу хотел сделать так, как вы описали. Но что то мне подсказывает, что это всё не более, чем игрушка)
Поэтому чтобы выкладывать какие то более правильные и фундаментальные сравнения, нужно ещё самому это всё поизучать на практике)
Отсюда и хаб "Ненормальное программирование", чтобы не было претензий о том, что это неприменимо на продакшене)
Более того, если мы взглянем на библиотечку TypeErasure из Boost'а (т.е. опять же очевидно, что уже не игрушка, а вполне используемое на практике), то увидим, что в ней центральную роль занимает слегка расширенная версия any, позволяющая реализовать все указанные вами в статье примеры приблизительно в одну строчку. )))
Объясните пожалуйста что означает этот код:
template < typename... Func >
class Visitor : Func... { using Func::operator()...; }
template < class... Func > make_visitor(Func...) -> Visitor < Func... >;
После объявления класса не стоит ;
, поэтому это не скомпилируется. Почему, при объявлении параметров шаблона в одном месте использован typename
, а в другом — class
? И самое главное — что означает последняя строка? Выглядит как User-defined deduction guides, но не компилируется из-за неправильного синтаксиса.
template <typename ...Func>
struct Visitor : Func... {
Visitor(Func ...f) : Func(f)... {}
using Func::operator()...;
};
template <typename ...Func>
auto make_visitor(Func...f) {
return Visitor<Func...>(f...);
}
Пример использования. Тут функторы должны быть copy-coonstructible
Работа с гетерогенными контейнерами с C++17