Comments 187
Нестрогая типизация c++ была не очень хорошей идеей
C++ - это в целом полигон для отработки идей, многие из которых оказались плохими.
Хотя, нестрогая типизация досталось ему от C.
А какие из новых идей, опробованных в C++, оказались хорошими?
Ну вроде как цпп отец раи, который имхо весьма полезная идея. Я вот когда был маленький и мне надоело в дельфи писать очередной типовой контейнер, но для другого типа, а мне кто то сказал что в цпп есть шаблоны - вот тут то все и завертелось. Цпп первым из мэйнстрим языков освоил обобщенный код. А вот ещё фундаментальная идея которая правда вот совсем не доделанная в цпп - валью семантика. Для меня этих 3х идей было 20+ лет назад по сравнению с явой, дельфи, пхп и шарпами было достаточно, сейчас уже не достаточно. Я не историк языков программирования, не знаю насколько свежими были эти идей на тот момент.
Ну вроде как цпп отец раи
CLU.
в цпп есть шаблоны
ML и параметрический полиморфизм. В C++ обкатывали разве что идею «как бы параметрический полиморфизм сделать максимально криво, но чтобы потом было весело делать головоломки и отсеивать людей на интервью». Хорошо разве что для job security.
валью семантика
совсем не доделанная в цпп
Здесь согласен.
В C++ обкатывали разве что идею «как бы параметрический полиморфизм сделать максимально криво, но чтобы потом было весело делать головоломки и отсеивать людей на интервью»
Интересно, а где в 1990-ом году это было сделано "максимально прямо" и почему эти мощные конкуренты (если они вообще были) до массового применения так и не дошли?
Потому что слишком не похожи на алгол были
Интересно, а где в 1990-ом году это было сделано "максимально прямо"
Написано в фразе прямо перед процитированной вами, но я повторюсь: в ML (ранние 70-ые).
Более того, даже тот же хаскель (с ещё более полиморфным полиморфизмом) появился в том же 90-м.
и почему эти мощные конкуренты (если они вообще были) до массового применения так и не дошли?
Полигон — это когда массовое применение прямо там же, да.
Не двигайте гоалпосты.
Полигон — это когда массовое применение прямо там же, да.
Массовое применение Haskell-я и ML?
Простите, но мне не интересны новости из параллельных вселенных. Как и ответы на выдуманные вами самими вопросы.
Не двигайте гоалпосты.
В вашем уютном манямирке, в котором Haskell уже массово применяется, возможно, и не знают про то, что не следует указывать людям что им делать, чтобы людям не пришлось указывать вам направление движения.
Массовое применение Haskell-я и ML?
Про необходимость массовости полигона была ирония. Извините, если получилось слишком неявно.
Как и ответы на выдуманные вами самими вопросы.
Вопрос о массовости вами задан не был? Ну ок (тоже ирония, если что).
Я так понимаю, что сейчас общаюсь с молодым человеком, который потратил всю юность и молодость на изучение C++, а потом горько пожалел об этом и опубликовал на Хабре статью про свое разочарование под другим ником.
Молодые люди какие-то, старые… О требованиях к полигонам мы согласны в итоге или нет?
О требованиях к полигонам мы согласны в итоге или нет?
Нет. Меня интересуют инструменты, которые широко применяются на практике, которые можно брать и использовать. C++ стал таким инструментом. Кому-то параметрический полиморфизм в C++ может казаться чем-то (или не казаться), но история показала, что это было востребовано и широко использовано. В отличии от.
Нет. Меня интересуют инструменты, которые широко применяются на практике, которые можно брать и использовать. C++ стал таким инструментом.
Только тут полигоны (о которых шла речь изначально) ни при чём.
Кому-то параметрический полиморфизм в C++ может казаться чем-то (или не казаться), но история показала, что это было востребовано и широко использовано. В отличии от.
Востребована и широко использована была совместимость с C, не более.
А то так сейчас дойдём до того, что востребовано и широко использовано UB в каждой второй строчке и remote code execution от попытки склеить две строки.
Только тут полигоны (о которых шла речь изначально) ни при чём.
Это в вашем манямирке.
Востребована и широко использована была совместимость с C, не более.
Если бы была востребована только совместимость с Си, то шаблоны в C++ никто бы не использовал. А они стали широко использоваться с середины 1990-х. И в рамках C++ эта реализация параметрического полиморфизма полностью и многократно доказала свою состоятельность.
Это в вашем манямирке.
Определение полигона требует массовости применения? Да/нет.
Если бы была востребована только совместимость с Си, то шаблоны в C++ никто бы не использовал.
Что за бредовая логика?
Если нужна совместимость с C, то выбирают язык, где эта совместимость даётся проще всего, не более. Требований на (не)использование других возможностей выбранного языка это не накладывает.
И в рамках C++ эта реализация параметрического полиморфизма полностью и многократно доказала свою состоятельность.
Как предмет для шуток, разве что.
Как предмет для шуток, разве что
Как я уже сказал, новости из параллельной вселенной меня не интересуют.
Вас интересуют только ложные силлогизмы и подмены тезиса, это я уже понял.
это я уже понял
Вы себе льстите.
C++ стал полигоном, но не таким, как ML и Haskell. Сделанное в C++ обкатывалось на практике на миллионах программистах и миллиардах строк кода. То, что было сделано в C++ нашло широкое применение и показало благодаря этому что получилось хорошо, что нет.
В отличии от загончиков, вроде Haskell.
Но вы, из своей параллельной вселенной, можете видеть все что угодно. И думать, что уже что-то поняли. Считать, что шаблоны в C++ нужны только для шуток, головоломок и job security.
То, что было сделано в C++ нашло широкое применение и показало благодаря этому что получилось хорошо
Окей. Открываю TIOBE — там питон на первом месте с трёхкратным отрывом от C++. Практика показывает, что экономия байтиков и микросекунд никому не нужна, и питон лучше, полезнее и качественнее, чем C++. Питон — лучший язык с ведущим подходом к безопасности (проверять типы во время выполнения куда лучше проверок во время компиляции, ведь там больше информации о контексте), наилучшим балансом производительности программиста и результирующего ПО, и самым богатым множеством библиотек. Подход питона к многопоточности показал, что форкать интерпретатор — эффективнее и быстрее всего, а треды в рамках одного процесса никому не нужны.
С++ (особенно современный) по-прежнему никому не нужен, кроме узкого загончика посетителей конференций, которые там занимаются интеллектуальной мастурбацией на никому не нужные монады, обсуждая, зачем их добавили в std::optional
, скажем.
Я всё правильно понял?
Я всё правильно понял?
Нет. Вы в очередной раз демонстрируете свою альтернативную одаренность. Но проблема Habr-а в том, что здесь не принято альтернативно одаренным персонажам указывать на их альтернативную одаренность. Хотя при общении с вами, например, аргументы ad hominem гораздо более уместны, чем попытки объяснить как устроен реальный мир.
Реальный мир — это когда качество и новизна определяются распространённостью, да (специально для вас — это снова ирония).
Когда плюсовика в очередной раз тыкают в то, что его любимый язык — сборище вековых наслоений из костылей, он в ответ кроет последним универсальным аргументом: «зато популярно!»
он в ответ кроет последним универсальным аргументом: «зато популярно!»
И что вы можете с этим поделать? В очередной раз рассказать, что все в Haskell-е все сделано "максимально прямо"?
Мир такой, какой он есть. Если вы сожалеете о впустую потраченной на C++ молодости, то это не значит, что все должны переживать о том, насколько C++ плох и бежать искать что-то лучше.
И что вы можете с этим поделать?
Мне с этим ничего не надо делать. Есть исходное высказывание про полигон, где ничего про популярность нет. Есть вы, влезающий в этот разговор с псевдоаргументом про популярность. Мне достаточно отметить, что тему разговора вы сменили, и всё.
В очередной раз рассказать, что все в Haskell-е все сделано "максимально прямо"?
Нет, конечно. В хаскеле много других вещей сделано довольно криво.
Но, кстати, если вы хаскелисту скажете, что стандартное prelude с partial-функциями кривое, или что без завтипов тяжело (и нормально их в хаскеле не сделают никогда по ряду причин), или тому подобные вещи, то хаскелист не будет вам отвечать «популярность хаскеля в ФП-среде означает, что partial-функции — самые функциональные функции, и что завтипы никому не нужны, хаскельная типизация самая типизированная типизация!»
Если вы сожалеете о впустую потраченной на C++ молодости, то это не значит, что все должны переживать о том, насколько C++ плох и бежать искать что-то лучше.
Я бы, конечно, мог сказать, что автор никому не нужной местечковой библиотеки для акторов конечно будет любить C++ и защищать его всеми силами (включая «популярно ⇒ хорошо»), но не буду.
Вместо этого, раз вам очень хочется обсуждать личности, скажу, что сожалею как раз о том, что потратил некоторую часть этой молодости на высшее образование, математику, и прочее. Без этого всего спокойно программировать на C++ и зарабатывать свои N тыщ в наносекунду — про жопсекурность шаблонов была не шутка — было бы куда проще.
Есть вы, влезающий в этот разговор с псевдоаргументом про популярность.
Вы опять ничего не поняли.
Популярность -- это только следствие того, что сделанное в C++ оказалось лучше, чем вы об этом думаете. Шаблоны в C++ оказались практичными, что и стало ключом к их широкому применению. И конкурентов у него тогда не было, даже если брать в расчет Ada.
Получилось, что C++ стал первым по настоящему большим полигоном, на котором этот самый параметрический полиморфизм массово испытывался.
То, что там не все хорошо -- это уже другая сторона медали.
Вы же ничего этого в упор не видите, поэтому вам кажется, что я меняю тему разговора.
Я бы, конечно, мог сказать, что автор никому не нужной местечковой библиотеки
При общении с вами есть еще и такая проблема, что вы никто и звать вас никак.
Популярность -- это только следствие того, что сделанное в C++ оказалось лучше, чем вы об этом думаете. Шаблоны в C++ оказались практичными, что и стало ключом к их широкому применению.
Вы снова повторяете свой псевдоаргумент о том, что если язык, имеющий нравящуюся вам фичу, популярен, то это говорит о (если вообще не «благодаря») качестве этой фичи.
«Практичные шаблоны C++» (особенно формата и качества реализации а-ля 90-ые) — вы тут на стендап-комика тренируетесь, что ли?
Получилось, что C++ стал первым по настоящему большим полигоном, на котором этот самый параметрический полиморфизм массово испытывался.
Не, маловат полигон. По-настоящему большой полигон — это только питон.
При общении с вами есть еще и такая проблема, что вы никто и звать вас никак.
Ну естественно, это вполне в канве общих отсылок к популярности, авторитетам и прочему подобному.
Я бы скорее удивился, если бы вы что-нибудь такое не сказали.
Вы снова повторяете свой псевдоаргумент о том, что если язык, имеющий нравящуюся вам фичу, популярен, то это говорит о (если вообще не «благодаря») качестве этой фичи.
Это реальность, которая вам не нравится и от которой вы бежите в свой манямирок.
Инструменты, вроде C++ или Python-а (да и Haskell-я, если я правильно помню историю его появления) создаются не для академических исследований, а для практического использования. Создаются, выпускаются в мир и дальше выживают те, кто практичнее (+ некоторое стечение обстоятельств).
C++ и Python выжили. Более того, они заняли значительную долю своей ниши. Haskell и Ruby где-то выживают.
«Практичные шаблоны C++» (особенно формата и качества реализации а-ля 90-ые) — вы тут на стендап-комика тренируетесь, что ли?
Если вы обосрались с C++ и теперь горько жалеете о бесцельно прожитых годах, то это ваши проблемы, которые про качества C++ не говорят ровным счетом ничего.
Я написал кучу кода на шаблонах, начиная с той самой середины 1990-х. Что-то из мной написанного используется совершенно разными людьми в совершенно разных проектах. Так что я-то знаю о чем говорю.
Более того, если бы в C++ не было шаблонов, то с большой долей вероятности я бы давно использовал что-то другое.
А тот факт, что вы анонимный никто, тогда как я здесь под своим реальным именем и любой может посмотреть, что и как я делаю, ставит нас в неравные условия: вы можете себе позволить попытаться унизить меня выражениями вроде "никому не нужной местечковой библиотеки" просто потому, что никто не может оценить то, что делаете вы. Ибо вы никто и звать вас никак.
Это реальность, которая вам не нравится и от которой вы бежите в свой манямирок.
Реальность — это «C++ популярный язык» (с чем я не спорю).
А вот «полиморфизм а-ля темплейты C++ — хорошее решение потому, что C++ популярный» — это манямирок, притягивание решения к желаемому ответу, и так далее. Проецировать и называть тех, кто на это указывает, живущими в манямирке, и выдавать свои нелогические цепочки за «реальный мир» — смешно.
Если вы обосрались с C++ и теперь горько жалеете о бесцельно прожитых годах, то это ваши проблемы, которые про качества C++ не говорят ровным счетом ничего.
Так обсёр-то в чём? Что не разделяю вашего восторга по поводу некоторой технологии и, о ужас, смею называть её костыльной?
Но фанатики при этом любители раста (или хаскеля, или чего там), лол.
Я написал кучу кода на шаблонах, начиная с той самой середины 1990-х. Что-то из мной написанного используется совершенно разными людьми в совершенно разных проектах. Так что я-то знаю о чем говорю.
Только о шаблонах вы не говорите. Вы вообще ничего по существу не говорите. Вы говорите только в канве «популярно ⇒ любой нравящийся мне аспект хорош».
Впрочем, это, конечно, не противоречит вашему «я знаю, о чём говорю», потому что, опять же, о технических вопросах вы не говорите.
И поэтому же несмотря на
А тот факт, что вы анонимный никто [...] Ибо вы никто и звать вас никак.
и на то, что там внизу страницы чувак, который для меня тоже анонимный никто, не имеющий ни предыстории, ни авторитета, ничего, с ним обсуждать что-то по существу получается (и мне вообще плевать, начинающий ли это программировать васян-восьмиклассник, или Страуструп и Саттер решили выучить русский язык и теперь вместе анонимно постят на хабре). Потому что там обсуждение конкретных технических вопросов.
А у вас и имя есть, и предыстория, и контекст, но от вас идёт только «популярно! обсёр! молодость! вы аноним!»
Метаиронично.
А вот «полиморфизм а-ля темплейты C++ — хорошее решение потому, что C++ популярный» — это манямирок, притягивание решения к желаемому ответу, и так далее.
А теперь внимание, вопрос №1: где я говорил, что полиморфизм в C++ -- это "хорошее решение"? Цитату, пожалуйста.
Практичность и состоятельность были, а вот "плохо", "хорошо", "отлично", "прямо" или "криво" -- вот это найдите в моих словах.
Перл, например, вполне себе оказался практичным и состоятельным. Как и Java. Что не мешает мне отзываться об этих языках в негативном ключе.
Что не разделяю вашего восторга по поводу некоторой технологии
Внимание, вопрос №2: где я высказывал хоть какие-то восторги по поводу сделанного в C++? Цитату, пожалуйста.
Получается так: вы говорите про "максимально криво", я прошу всего лишь две вещи:
a) сказать, где же было "максимально прямо". И, вполне ожидаемо, мне приводят в пример то, что востребовано долями процентов от общего числа разработчиков ПО. Да и среди тех, у кого востребовано, изрядная доля не программистов, а ученых из области computer science (т.е. не имеющих отношения к промышленной разработке софта);
b) объяснить почему эти самые конкуренты, у которых "максимально прямо", не смогли получить такого же широкого распространения, как C++. И ответа на этот вопрос нет вообще. А если кто-то всерьез думает, что причина в "непохожи на Алгол", то у меня для них плохие новости.
Ваши высказывания похожи на "У "Ласкового мая" максимально примитивная плохая музыка, тогда как у Дебюсси..." Только вот если нужно собирать стадионы, то с "Ласковым маем" это возможно, а вот с Дебюсси -- большой вопрос. И проблема в том, что в оценку творчества "Ласкового мая" кроме "максимально примитивный" (что так и есть) начинают добавлять еще и "плохой".
Так же и по поводу C++: из того, что в C++ что-то делано примитивно и не так, как в Haskell, не следует то, что это "плохо", "криво" (или еще какой-то уничижительный эпитет).
И вот вместо того, чтобы задуматься о том, почему же в реальном мире распространение получают отнюдь не самые "максимально прямые" инструменты (C++ и только что упомянутый Perl тому примеру) вы начинаете видеть в моих словах "полиморфизм а-ля темплейты C++ — хорошее решение" и "вашего восторга по поводу некоторой технологии", после чего спорите с тем, чего я не говорил.
Отсюда и вытекает, что в вашем случае аргументы ad hominem более уместны, чем попытки объяснить чем реальный мир отличается от вашего манямирка.
Так обсёр-то в чём?
В том, что вы сожалеете о годах, потраченных на C++. Более того, все еще хуже, если здесь вы говорите правду:
сожалею как раз о том, что потратил некоторую часть этой молодости на высшее образование, математику, и прочее
Вам кажется, что где-то в жизни вы сильно ошиблись, и поэтому теперь бегаете по статьям о C++ чтобы всем рассказать, насколько C++ плох. Что очень похоже, что вы сами обгадились, а теперь носитесь с полными шароварами и кричите, что виноват C++.
Так вот, C++ никак не может быть виноват на то, что сейчас вам кажется, что какую-то часть жизни вы прожили не так.
Когда же вы бегаете по Хабру и высказываетесь в духе:
"чтобы потом было весело делать головоломки и отсеивать людей на интервью». Хорошо разве что для job security."
"Как предмет для шуток, разве что."
"«Практичные шаблоны C++» (особенно формата и качества реализации а-ля 90-ые) — вы тут на стендап-комика тренируетесь, что ли?"
это говорит о том, что вы упорно пытаетесь закрыться от реальности измазывая C++ черной краской в силу своих личных обид.
Потому что там обсуждение конкретных технических вопросов.
А выяснение причин, почему одна технология стала гораздо более востребованной, чем другая, выходит за рамки конкретных технических вопросов. Вам от этого, наверное, неуютно, раз вы попытались унизить меня дав оценку тому, что я делаю.
Только и здесь вы продемонстрировали свою альтернативную одаренность: вы отрицаете аргументацию вида "раз популярно, значит хорошо", но зачем-то приплетаете "никому не нужной местечковой". Видимо, аргументы о популярности внезапно начинают работать когда это вам удобно.
и нормально их в хаскеле не сделают никогда по ряду причин
Почему?
«Avoid success at all costs!»
Увы и ах, но пока SPJ жив, Haskell обречён оставаться вечной альфой. Такой сюжет на древнегреческом называется трагедией — вроде все всё делают правильно, но получается дрянь.
Её же следует читать как "avoid (success at all cost)", а не "avoid success, at all cost".
Про альфу — а что именно в хаскеле по-вашему делает его неподходящим для прода?
Две основных причины, на мой взгляд:
Куча уже написанного кода, с ленью, отсутствием разделения данных и коданных, и прочими вещами, которые мешают иметь консистентную метатеорию. Неконсистентные завтипы — это точно не «нормально».
По беглому общению с парой человек, наиболее активно работающих над завтипами в хаскеле, им важнее сделать хоть что-то, но быстрее, чем нормально, но потом. Один товарищ, который по потенциальным завтипам в хаскеле вообще диссер сделал и потом за них топил следующие лет 10, вообще считает, что консистентность-то и не нужна вообще. Когда я у него спросил, мол, что хотелось бы быть уверенным, что если я доказал что-то, например, про свой инстанс
Monad
, что законы там всякие выполняются, он ответил, мол, что да, полезно, но надо сделать скорее.
Так что нет, это совсем не avoid success at all costs из соседнего комментария. Это, наоборот, стали гнаться за успехом.
Вероятно, мы по разному понимаем "нормально". Хаскель это всё же больше язык программирования, чем верификатор, поэтому я не думаю, что консистентность важна. Подход как у идриса для хаскеля, имхо, лучше чем как у агды, потому что бодаться с termination checker-ом довольно трудно
Вероятно, мы по разному понимаем "нормально". Хаскель это всё же больше язык программирования, чем верификатор, поэтому я не думаю, что консистентность важна.
Даже в языке программирования я хочу знать, что если я, например, написал какую-то свою свободную монаду, то она удовлетворяет тем законам, которые я от неё ожидаю в предметной области. Или что там монады эти — что какое-то нетривиальное преобразование имеет, скажем, правую (или левую) обратную (то есть, что lossless-сжатая картинка правильно разжимается).
Если метатеория языка не является консистентной, то я не знаю, доказал ли я что-то на самом деле, или у меня всё моё доказательство на самом деле сводится к
someGoodProperty : ∀ x → foo x = bar x
someGoodProperty x = someGoodProperty x
Иными словами, даже в языке программирования полезно иметь возможность выкинуть квикчек и использовать нормальные доказательства (для тех свойств, которые не завязаны на внешний мир).
Подход как у идриса для хаскеля, имхо, лучше чем как у агды
В идрисе можно сделать %default total
, и будет как в агде :] И тайпчекер идриса помнит, что тотально, а что — нет.
Папиров с доказательством его консистентности, мягко скажем, меньше написано, но идрис хотя бы претендует на то, чтобы быть консистентным языком в total-подмножестве. Значимая часть людей, топящих за завтипы в хаскеле, не ставит целью даже это.
потому что бодаться с termination checker-ом довольно трудно
ИМХО в практических задачах это не проблема, и синтаксического уменьшения терма (или достаточно очевидного accessibility relation) хватает. Серьёзное бодание с termination checker'ом вылезает только при попытке в языке формализовывать другие языки. Но это так, личный опыт и опыт чтения чужих статей, не более, могу ошибаться.
Это, наоборот, стали гнаться за успехом.
Успехом конкретного PhD-candidate, я бы сказал. Абсолютно положив на экосистему.
Нет, конечно. В хаскеле много других вещей сделано довольно криво.
Мне не нравится неявность мемоизации. В конце-концов, это одна из тех оптимизаций, которая должна быть засунута в спецификацию языка. Ну как TCO в ML.
В Клине сделано лучше, хотя тоже не идеально:
biglist1 = [1..10000] // a constant function (if defined globally)
biglist2 =: [1..10000] // a graph (Vkni — function to evaluate)
biglist3 => [1..10000] // a constant function
https://clean.cs.ru.nl/download/html_report/CleanRep.2.2_12.htm#_Toc311798110
Читать как вы с кем то сретесь на Хабре это половина всего валью от хабра имхо, но оппонент набросил все ж таки значимый аргумент про популярность, от которого нельзя просто так отмахнуться. Давайте я попытаюсь переформулировать - представте, что вы Бьёрн и сейчас конец 70х как запилить язык совместимый с сишечкой и добавить дженерики, а ну тем ещё что то про перформанс в рантайме никаких гц и не платить за то чё не используется ну и что бы рядовому сишнеку было понятно как этим пользоваться? Я думаю вы если вам очень нечем заняться ради лулзов можете предъявить транспилятор в сишечку (что Бьёрн и сделал) в котором можно в хорошие дженерики и нормальная такая валью семантика и ну и что там вам ещё для счастья не хватает зав типы. Потом перед релизом не забудьте бороду отрастить если ещё нету, как зарелизите можете сбрить если не нравится.
Практика показывает, что экономия байтиков и микросекунд никому не нужна, и питон лучше, полезнее и качественнее, чем C++.
Современный уровень копроративного менеджмента таков, что более эффективные языки в кровавом ынтерпрайзе не могут быть использованы. А вечно недоделанный Пистон, который невозможно использовать без периодического подпиливания, принимается копроменеждментом на ура.
Хотя казалось бы.
проверять типы во время выполнения куда лучше проверок во время компиляции, ведь там больше информации о контексте
И тесты делать во столько же раз сложнее по току же причине
Прикольная фамилия у этого Мэтта Годболта: дословный перевод "Божественный болт" или "Болт господень"? Знатоки английского - как правильно?
А как вы в Rust сделаете это некорректное преобразование в u64?
И правда. Пожалуй это недостаток, что более опасный способ сделан как более лёгкий для использования (x as u64 проще написать чем u64::try_from(x))
Подобное правилами линтера отлавливается:
clippy::as_conversions
clippy::cast_lossless
clippy::cast_possible_truncation
clippy::cast_possible_wrap
clippy::cast_precision_loss
clippy::cast_sign_loss
clippy::char_lit_as_u8
clippy::fn_to_numeric_cast
clippy::fn_to_numeric_cast_with_truncation
clippy::ptr_as_ptr
clippy::unnecessary_cast
invalid_reference_casting
Неужели в компиляторах С++ нет возможности включить такую же строгую типизацию?
А я еще раз упомяну о другом аспекте, заставившем меня пару лет назад окончательно перейти на Rust. У C++ до сих пор экосистема из 80ых, ее просто нет в едином виде.
Добавление библиотеки почти всегда квест. Многие библиотеки их
быдлокодерамиразработчиками предлагается собирать и ставить по уникальной инструкции прямо в глобальное окружение (!) для того чтобы использовать в проекте.Есть сборщик CMake, который состоит на 100% из костылей и по хорошему никогда не должен был существовать. Минимальный проект заводится с простыней кода конфигурации на отдельном недоязыке (обычно ее копируют из старого проекта, поскольку почти никто не понимает как оно работает).
Есть менеджеры зависимостей Conan и vcpkg, которые чуточку исправляют ситуацию, но с отдельными registry, плохой совместимостью между собой, точно также обложены лютыми костылями и точно также требуют конфигурации на ровном месте.
Наконец есть пожалуй единственный пригодный на текущий момент менеджер сборки и зависимостей, xmake, но у него небольшая экосистема, и проблемы низкоуровневых зависимостей зачастую самым неожиданным образом протекают наверх, сборка падает, и снова начинается ад с костылями и ручным разруливанием различной конфигурации.
Именно из-за всего этого ада в мире C++ стали настолько популярны header-only библиотеки, состоящие из одного заголовочного файла. Вовсе не из-за особых хакерских умений или минимализма разработчиков, а просто для того, чтобы их поделия вообще можно было хоть как-то подключить в другой проект.
А что же в Rust? А в Rust, без преувеличения, лучший менеджер зависимостей среди всех известных мне языков — Cargo, и одна общая экосистема на всех, что дает языку огромный буст к развитию. Чтобы сборка завелась нужно написать... ноль конфигурации! Из коробки действуют разумные соглашения по умолчанию, код лежит в src, точка входа в main.rs. Хотите подключить библиотеку? Одна строчка в Cargo.toml. Хотите подключить из своего registry или из git'а? Уложитесь в ту же строчку. Именно эта простота, а не семантика перемещения или иные языковые особенности, на мой взгляд позволила Rust'у так быстро обойти C++.
Да, Rust немного посложнее синтаксически, это фанатам C++ тяжело принять, но разобраться того стоит. Все лучшие практики из C++ core guidelines в Rust внедрены и проверяются на уровне языка.
А ещё появился свеженький rustrover и теперь тулинг просто отличный!
CMake не только для С++, честно говоря.
Но как всегда (в жизни, по закону бутерброда) - самый отвратительный билдтул стал "стандартом".
Да, Rust немного посложнее синтаксически, это фанатам C++ тяжело принять, но разобраться того стоит.
Не то, что бы Rust от этого становился сложнее C++. В С++ вся эта сложность заботливо перенесена в стандартную библиотеку и компиляторы. :)
Но порог входа выше, да.
Да, Rust немного посложнее синтаксически, это фанатам C++ тяжело принять, но разобраться того стоит. Все лучшие практики из C++ core guidelines в Rust внедрены и проверяются на уровне языка.
Вообще то Раст гораздо проще плюсов, просто неудобнее.
Уберите экслюзивное владение обычными переменными (не ресурсами) , сохранив преимущества, и был бы язык года (ну почти)
только Cmake научился так же с гита напрямую зависимости подтягивать и аккуратненько в папке проекта складывать, синтаксис приличный если писать на новых версиях и изначально правильно, а так, любая либа легко заводится с 2 команд указанных в гите, не говоря уже про новые системы сборки, которые и синтаксически просты и с зависимостями там дела лучше обстоят
Через полгода ждем статью "как я перешел с руст на ххх т.к. руст не годится для чтего-то сложнее хеллоу ворда"
И clang 19, и gcc 14 примут этот код без жалоб, даже с
-std=c++23 -Wall -Wextra -Wpedantic
А есть идеи зачем было сделано так бестолково? Это же явно намеренное поведение компилятора.
Ну вообще-то флаги, заставляющие компилятор делать соответствующие проверки, существуют - -Wconversion
, -Wsign-conversion
, и так далее, просто они не включены по умолчанию. А не включены они по умолчанию потому, что существует дофига абсолютно валидного и беспроблемного кода типа такого:
void foo(unsigned x) { ... }
void bar()
{
int y = baz();
if (y < 0) return;
foo(y);
}
Те, кому надо, могут включить.
Можно включить, только лог сборки сразу будет завален сотнями и тысячами предупреждений из-за используемых зависимостей в любом мало-мальском проекте, что делает подобные опции малополезными. В Rust это решено концептуально и на корню.
Этот код появился потому что компилятор не ругался. Вопрос в том, почему он вообще позволял небезопасное неявное приведение типа.
А тут не на что ругаться, абсолютно ничего "небезопасного" в приведенном мной коде нет. Прописывание явного приведения никакой "безопасности" тут не добавляет.
Действительно, что может быть опасного в присвоении знакового значения в беззнаковое поле или наоборот..
Безопасности тут добавляет проверка if
(y < 0)
, а не явное приведение y
к unsigned
. Если есть проверка, то явное приведение не добавляет никакой "безопасности". Бездумное приведение же без проверки просто заткнет компилятор, а ошибка останется - как и в расте, кстати.
С точи зрения простого компилятора эта проверка ничего не добавляет. И даже если компилятор научить сужать типы (а это не про С), это не поможет с проверками вида if( x < y )
.
С точи зрения простого компилятора эта проверка ничего не добавляет.
Это уже другой вопрос. Для надежного отлавливания ошибок конверсии между знаковыми и беззнаковыми типами одного требования явного приведения мало - прописывание этого приведения само по себе ничего не дает, ошибка остается. Если в приведенном мной куске кода завтра один джун пропишет foo((unsigned)y)
, чтобы заткнуть компилятор, а послезавтра другой джун выкинет if
по ошибке, то компилятор ничего не заметит (как и в расте, кстати).
Проверка не является одним целым с вызовом и при очередном рефакторинге может быть (ошибочно) стёрта.
Я бы не назвал такой код «абсолютно валидным и беспроблемным». Если программист хочет взять на себя ответственность — пусть делает это явно.
Проверка не является одним целым с вызовом и при очередном рефакторинге может быть (ошибочно) стёрта.
Конечно! Но вся штука в том, что точно так же она может быть ошибочно стерта и при наличии явного приведения foo((unsigned)y)
, и компилятор тут вам ничем не поможет.
Если компилятор заставит написать foo((unsigned)y)
, чтобы при поиске ошибки у читающего возник вопрос, а почему не foo(y >= 0 ? (unsigned)y : 0)
(второй 0 можно заменить на логгирование/исключение/::exit(-146)
/whatever), я бы сказал, он свою задачу выполнил.
а почему не
foo(y >= 0 ? (unsigned)y : 0)
(второй 0 можно заменить на логгирование/исключение/::exit(-146)
/whatever)
Ну вообще это далеко не всегда нужно, особенно если ситуация когда y < 0
вполне штатная - просто в этом случае вызывать foo()
не нужно вообще. Лично я предпочитаю использовать контекстозависимые линтеры типа сонара, в которых предупреждение можно пометить как "accepted", но при изменении контекста предупреждение будет расценено как "новое" и будет снова отображено. А явные касты тут только мешают.
Я собрал данный код на clang-19 и gcc-14, действительно ошибки безопасности здесь никакой нету, приведение вполне себе явно из-за проверки, компилятор не выдает ошибки. Стандартом для компиляторов специально эти варнинги отключены, так как они часто ложные срабатывания имеют и не проверяют пользовательские проверки переменных как `if (y <0)`
Не надо делать лишние явные приведения, компилятор сам сообщит когда действительно опасные действия Вы делаете с типами, особенно хорошо по-умолчанию работает у clang компилятора такое.
большинство программ обрабатывают данные, которое проходят через миллион мест, никакой язык, ни какие методы не помогут на этапе компиляции.
для всего этого придумали системы обработки ошибок, контракты и т.п.
не нужно решать высокоуровневые, прикладные задачи, средствами, конструкциями языка.
С другой стороны, существует немало задач которые могут быть решены и успешно решаются конструкциями языка и компилятором - не присваивай значение неправильного типа, не устраивай гонок данных между потоками, не используй память после освобождения и т.п. Эти средства позволяют не допустить подобный невалидный код в принципе и доказали свою эффективность. А отрицать их это просто луддизм
Нет, такие вещи не решаются методами раста. Вы можете запретить многопоточное программирование вообще и сказать, что это решение проблем гонок - но если вам всё таки нужно написать что-то многопоточное, вам не поможет здесь раст никак.
Максимум он избавит студента от глупой ошибки, именно что студента и от глупой. А обычному программисту он просто вставляет палки в колёса, не давая делать то что нужно
Вас не смущает, что почти все программисты на Rust это и есть бывшие программисты на C++? Заметил, что язвительные комментарии пишут те, кто просто не осилил переход. Воспринимайте Rust как улучшенный C++, также как C++ был улучшенным Си, и все встанет на свои места.
Вас не смущает, что почти все программисты на Rust это и есть бывшие программисты на C++?
Я вот заметил, что вы очень любите делать широковещательные и ничем не подкрепленные заявления. Откуда дровишки что "почти все программисты на Rust это и есть бывшие программисты на C++"? Где можно ознакомиться с соответствующей статистикой?
Общеизвестный в сообществе факт, что Rust разработан людьми, кто устал от проблем C++, как улучшенная версия C++ (достаточно взглянуть на первые черновики), и целевая аудитория способных вкатиться в такой сложный язык это, разумеется, программисты на плюсах. Этот факт также вполне подтверждается эмпирическим опытом. Перед вами очередной живой пример.
Где можно ознакомиться с соответствующей статистикой?
Никто не ведет общую статистику переходов между различными языками. Полагаю вы это прекрасно знаете. Также как не найти, например, статистики о том, что первые C++ программисты это прокачавшиеся сишники.
Судя по вашим комментариям в различных постах про Rust, у вас (и некоторых других) какое-то C++ сектантство в духе конторы на букву "я", вынуждающее вас оставлять агрессивные комментарии и прибегать к демагогическим приемам. Вполне могу понять вашу боль и разочарование, ведь наверняка вы посвятили немалую часть жизни чтобы разобраться во всех нюансах старого языка, однако происходит ни что иное, как естественный переход сообщества на более развитую и мощную технологию.
Ну то есть в своих широковещательных заявлениях основываетесь не на какой-то статистике, а на собственных каких-то представлениях "из головы" и собственном опыте. Тогда стоит добавлять к своим заявлениям "ИМХО", а не преподносить их как факт. Не ведите себя как растофанатик, ведите себя как инженер.
какое-то C++ сектантство в духе конторы на букву "я", вынуждающее вас оставлять агрессивные комментарии и прибегать к демагогическим приемам
Здорово, здорово. Нечем подкрепить свои утверждения - обвини собеседника в сектантстве и демагогических приемах. С вами я на обозримое будущее разговор закончил.
Ну вот, вы ушли в какую-то метарефлексию вместо продуктивного обсуждения. Не так сложно заметить что вы любите C++ настолько трепетной, личной любовью, что вас глубоко задевает обсуждение минусов этого языка, и сам факт что многие стали предпочитать Rust. Это и есть самое настоящее сектантство, бесконечное повторение одних и тех же мантр про то что "любимый C++ не хуже", для вашего собственного самоуспокоения)
С вами я на обозримое будущее разговор закончил.
Видимо для этого вам понадобилось несколько раз отредактировать комментарий до его нынешней формы...
Причем сам он точно также использует эти приёмы.
"Общеизвестный в сообществе факт..." - это приём называемый "апелляция к очевидности".
Общеизвестный в сообществе факт ...
у вас какое-то C++ сектантство ...
вынуждающее вас оставлять агрессивные комментарии и прибегать к демагогическим приемам.
Как достали сектанты и демагоги-любители набрасывать отходы деятельности на вентиляторы обвиняя всех не согласных с их ржавой религией.
Это очень легко и очевидно доказывается:
Большинство понимает английский. Но некоторые из них, изучают ещё и эсперанто.
Значит, эсперанто лучше английского!
Думаю, даже статистику несложно найти.
Заметил, что язвительные комментарии пишут те, кто просто не осилил переход
у меня как раз другое мнение: перешли те, кто не осилил С++
И потом большинство из них разочаруется в расте, перейдёт дальше в в джаву или go или обратно в С++
Воспринимайте Rust как улучшенный C++
вы зря думаете, что я не знаю что такое rust. Наоборот, я знаю о нём столько, что точно понимаю, что он ухудшенный С++
Наоборот, я знаю о нём столько, что точно понимаю, что он ухудшенный С++
Расскажите, пожалуйста, поподробнее и, если можно, с примерами для иллюстрации
на расте гораздо меньше возможностей и гибкости при создании своих типов, знаменитые примеры это то что на расте очень сложно и с точки зрения идиоматичного раста невозможно написать список, деревья, графы, интрузивные контейнеры
Очень много борьбы с самим языком вместо работы над задачей, так как избыточные правила раста зачастую false positive
УБ при наличии двух ссылок (хотя бы одна из которых мутабельная) на любой объект, очень нелогичное уб которое очень просто получить в расте
Нет полного описания языка, в том числе полного списка уб которые в нём есть. Как можно писать код, не зная каковы правила? По сути неизвестно, что можно в unsafe коде, а что нельзя
При создании своих абстракций неизбежно сталкиваешься с FantomData (костылём) Pin (костылём) и невозможностью написать self-reference типы
Везде макросы, потому что язык слаб в шаблонах (джнериках). При этом макросы куда хуже сишных, это отдельный какой-то регекс язык
Муторный синтаксис, язык настроен так, что при любом самом мелком изменении приходится менять много кода. Программисты постоянно пытаются этого избежать, результат это повсеместные .unwrap(), as, into _ и матч с плейсхолдером, которые возвращают ту же проблему из статьи - into это и есть неявное приведение
Плохие стеклес корутины, даже сами растеры признаютчто async не удался, он совершенно не может работать в терминах лайфтаймов
и так далее
на расте гораздо меньше возможностей и гибкости при создании своих типов
На самом деле больше. Что-то, а вот типы это сильная сторона Rust'а.
с точки зрения идиоматичного раста невозможно написать список, деревья, графы, интрузивные контейнеры
Можно, но сложно. Все это есть в как в стандартной библиотеке, так и во множестве крейтов. А как часто вы пишите собственные списки и главное, зачем?
Очень много борьбы с самим языком вместо работы над задачей,
На самом деле Rust разворачивает ошибки которые выстрелили бы в runtime, в compile-time. Мне например очень приятно получать эти сообщения компилятора, ведь код правится довольно легко.
так как избыточные правила раста зачастую false positive
Иногда, но это лучше чем помнить в уме и вручную воспроизводить практики из C++ core guidelines, которые вообще-то при их применении зачастую также избыточны. В Rust все это заботливо подсказывает компилятор, а мы сосредотачиваемся на главном — писать код и продумывать логику.
УБ при наличии двух ссылок (хотя бы одна из которых мутабельная) на любой объект, очень нелогичное уб которое очень просто получить в расте
Здесь явно не хватает примера.
Везде макросы, потому что язык слаб в шаблонах (джнериках). При этом макросы куда хуже сишных, это отдельный какой-то регекс язык
Можно про C++ сказать "везде шаблоны" и справедливо заметить что у них вырвиглазный синтаксис и абсолютно упоротая логика работы, по сути еще один ЯП внутри ЯП. А макросы в Rust не имеют ничего общего с макросами C++. Это вопрос терминологии.
При создании своих абстракций неизбежно сталкиваешься с FantomData (костылём) Pin (костылём) и невозможностью написать self-reference типы
Не сталкиваюсь, но я и свои списки обычно не пишу)
Муторный синтаксис, язык настроен так, что при любом самом мелком изменении приходится менять много кода. Программисты постоянно пытаются этого избежать, результат это повсеместные .unwrap(), as, into _ и матч с плейсхолдером, которые возвращают ту же проблему из статьи - into это и есть неявное приведение
"при возникновении исключения дайте программе упасть" сказал еще Страуструп. Во вторых, зачем вам везде вызывать unwrap и делать матч с плейсхолдером? В Rust, опять же, благодаря мощной системе типов, это решается вот так, например
#[derive(Error, Debug)]
enum UploadingError {
#[error("HTTP request failed: {0}")]
ReqwestError(#[from] reqwest::Error),
#[error("URL parsing failed: {0}")]
UrlParseError(#[from] url::ParseError),
#[error("XML processing failed: {0}")]
XmlProcessingError(#[from] quick_xml::DeError),
#[error("JSON processing failed: {0}")]
JsonProcessingError(#[from] serde_json::Error),
#[error("API returned an error: status={status}, body={body}")]
ApiError { status: StatusCode, body: String },
}
Как это решается в C++? У вас даже зная родительский тип, на стеке подтип исключения нельзя перехватить, поэтому часть ловит указатели, выделяя память под ошибки (и смех и грех), часть таких контор как Google, например, пишет вообще без исключений, внезапно, имитируя подход Rust. Вот так незадача да?
Плохие стеклес корутины, даже сами растеры признаютчто async не удался, он совершенно не может работать в терминах лайфтаймов
Это более общая проблема. Идея async/await в Rust/C#/Kotlin неудачная в первую очередь из-за цветовой дифференциации. Единственная хорошая реализация кооперативной многозадачности это виртуальные потоки jdk21.
УБ при наличии двух ссылок (хотя бы одна из которых мутабельная) на любой объект, очень нелогичное уб которое очень просто получить в расте
Ну так borow-checker и не даст создать вторую ссылку
Плохие стеклес корутины, даже сами растеры признаютчто async не удался, он совершенно не может работать в терминах лайфтаймов
Отлично они работают с лайфтаймами. В большинстве случаев будет просто работать. Причем компилятор соберет все корутины в одну и будет крутить ее без дополнительных аллокаций. В asio надо всегда помнить, что буфер должен жить достаточно долго, а в расте компилятор всегда об этом напомнит.
Вообще стаклес корутины в расте не особо то отличаются от стаклес корутин в C++. Есть некоторое количество удобностей, например ключевое слово async, которое превратит функцию в корутину, но можно и самостоятельно реализовать трейт и тогда он будет иметь все те же возможности, что и в плюсах. Единственное принципиальное отличие - наличие типажей типа Waker'а, которые позволяют интегрироваться с асинхронным рантаймом.
Вообще асинк в расте настолько простой и понятный, что его тривиально интегрировать с теми же плюсами как в одну сторону, так и в другую. В плюсах же подружить какой-нибудь asio и kj - то еще приключение.
Напишу на что не ответили другие.
into это и есть неявное приведение
Вероятно вы путаете неявное приведение и автоматический вывод типов. Или вы скажете, что auto a = ... в С++
тоже неявное приведение?
Вот это неявное приведение: uint8_t a = -100
. А вот это автоматический вывод типов: let a: u8 = (-100).into()
.
При этом макросы куда хуже сишных, это отдельный какой-то регекс язык
<sarcasm on> Да да, препроцессор гораздо лучше разбора AST. Это просто сказка! <sarcasm off>
Муторный синтаксис, язык настроен так, что при любом самом мелком изменении приходится менять много кода.
Утиная типизация в С++ тоже имеет свои плюсы и минусы. Лично я думаю, что минусы перевешивают. Лучше написать чуть больше кода, но иметь больше контроля.
потому что язык слаб в шаблонах (джнериках)
Единственное с чем соглашусь. Но это пока, посмотрим через пару лет.
Вероятно вы путаете неявное приведение и автоматический вывод типов
нет, я ничего не путаю. Как можно называть выводом типов то, что явно описано в документации как
A value-to-value conversion that consumes the input value
И? Там нет слова "неявное", что логично, т.к. .into()
надо явно написать.
Давайте приведу другой пример, тут уж должно быть абсолютно очевидно. Неявное приведение:
void func(uint8_t a) { ... }
func(-100)
Явное приведение с автоматическим выводом типа:
fn func(a: u8) { ... }
func((-100).into())
Если убрать .into()
, то код не скомпилируется, т.к. в Rust сильная типизация, и неявных преобразований нет.
Вот в плюсах это же целое дело, каждый раз смотреть, можно ли тип между тредами передавать или нет. Это только в документации есть. А если ее нету, а как понять.
Ошибка эта вот довольно распространенная и тяжело уловимая. Раст просто не даст ее сделать.
D тоже не даст, но при этом не накладывает столь жёстких ограничений на ссылки, как Rust.
большинство программ обрабатывают данные, которое проходят через миллион мест, никакой язык, ни какие методы не помогут на этапе компиляции.
Зависимые типы помогут (и помогают).
А почему он продал тебе именно руст а не голанг? Видимо, тебе очень хотелось написать хвалебную статью про руст, но никак повода не находилось?)
И он аж целых двадцать лет писал на плюсах, но не знает, что использовать для денежных типов. На 21 год загляни уже в шарп, наконец
В голанге достаточно своих способов стрелять в ноги, не говоря об очень ограниченной выразительности языка.
Если говорить о примере из статьи:
С int и float там всё в порядке, типизация строгая;
С Quantity и Price в принципе нормально. Разве что там где раст потребует явно указывать тип даже у статических значений (например, можно указать только
Quantity(100)
), го допускает простое100
(скорее минус по читаемости, чем по наличию багов).На счёт парсинга строки. Проверка ошибок в го не обязательна, т.к. результат и ошибка возвращаются как два отдельных значения (а не одним значением как в раст). Программист вполне может проигнорировать (или забыть) проверку err значения и использований невалидный результат. К счастью ошибочный результат парсинга будет ноль, но в общем случае никто не гарантирует что там безопасное значение.
Конечно есть библиотеки для го, которые добавляют генерик Either тип, но из-за того что в го нет нормальных енамов и генериков, использовать Either тип это тот ещё геморрой.Отдельное веселье это парсинг строк в го, потому что динозавровая стандартная библиотека ещё до генериков не доросла. Там ParseUint всегда возвращает uint64. Если вам нужен какой-нибудь uint16, то придётся делать конвертацию uint64 -> uint16, за что на вас уже накричат линтеры (в общем случае то преобразование небезопасное)...
Ну это очередная попытка продать, чтобы продать, тому кому этого не нужно (заведомо неверным способом), потому что, ну а может быть нужно?. Ради справедливости, на всех известных мне биржах (ну то есть на всех, кроме тех, которые, если бы я встретил, меня удивили бы), что quantity, что price это FixedPoint. Прошли уже те времена (лет 25-30 назад), когда из-за ошибок округления можно было потерять или сделать состояния.
Ничего удивительного, C++ всегда держал разработчика на поводке достаточной длины чтобы выстрелить себе в ногу.
Проблема тут вообще не в неявном преобразовании int/float. Проблема в отсутствии именованных аргументов, которая встает во весь рост, если, например, нужно передавать большие пачки float-параметров. Как это сделать красиво и надежно - непонятно. Делать по классу на каждый параметр - вообще не вариант. Создавать вспомогательный pod-тип для каждой такой функции - при ее вызове можно легко забыть проинициализировать один из членов (это в C++, в Rust при инициализации pod-типа - нет такой проблемы).
Проблема как раз таки в неявном преобразовании.
Передавать большие пачки float-параметров это моветон. И это проблема легко решается через Parameter Object. Конструктор не даст оставить поля без инициализации. Можно еще std::optional
заюзать. Вызов сеттеров можно красиво сделать через fluent interface. Короче вариантов куча.
Пачка float-ов в конструкторе Parameter Object ничем не лучше.
Setters / std::optional позволит только добавить runtime проверку.
Некрасиво, неудобно, ненадежно.
Если мы про плюсы, то как раз parameter object для пачки float-ов это моветон. Пачка флоатов однозначно будет передана через SIMD-регистры, а вот объект по стандарту должен быть где-то в памяти (при вызове - на стеке). Надеяться, что компилятор это соптимизирует, ну такое.
Делать по классу на каждый параметр - вообще не вариант.
Если по условиям задачи можно отойти от этих двух языков, то в обычном кондовом Haskell 98 есть такая штука как newtype
. Одна-две строки и псевдоним готов.
Но сначала он обращает внимание на количество (
quantity
) и стоимость (price
), подчеркнув, что C++ сильно усложняет защиту вызывающей стороны от ошибок: компилятор допускает и 1000.00 в качествеquantity
, и 100 в качествеprice
, не выдавая никаких предупреждений, несмотря на то, что они относятся к другим типам. Он просто выполняет преобразования.
Наверное, прежде чем "продавать" что-то сомнительное, следовало бы научиться пользоваться C++:
#include <cstdlib>
#include <iostream>
template <typename T1, typename T2, typename T3>
void sendOrder(const char *symbol, T1 buy, T2 quantity, T3 price) = delete;
void sendOrder(const char *symbol, bool buy, unsigned quantity, double price) {
std::cout << symbol << ' '
<< std::boolalpha << buy << ' '
<< quantity << ' '
<< price << std::endl;
}
int main() {
sendOrder("DAL", false, 10u, 5.0);
#if 0
sendOrder("DAL", 1, 10u, 5.0);
sendOrder("DAL", false, 10, 5.0);
sendOrder("DAL", false, 5.0, 10);
#endif
return EXIT_SUCCESS;
}
При попытке раскомментировать за'if
'-0-енное, компилятор отказывается компилировать.
Почему не было даже попыток применить данное решение?
В результате вы продадите 4294967196 акций и обанкротитесь.
Человек, утверждающий подобное, даже не пытался изучить предмет.
Не каждый эмитент имеет столько выпущенных акций.
Хорошо, пусть речь идёт о таком эмитенте, который выпустил столько или большее количество акций.
Очевидно, что обычный трейдер не обладает таким количеством акций, и чтобы их продать, их необходимо зашортить, то есть, сначала занять у брокера.
Вряд ли у брокера есть столько акций, но даже если есть, он не выдаст акции взаймы просто так, а — только под залог депозита или его части.
У подавляющего большинства трейдеров нет столько денег на депозите.
Хорошо, предположим, что и деньги есть, и у брокера есть необходимое количество акций.
Чтобы их продать, необходимо, чтобы их кто-то сразу купил, а для этого в биржевом "стакане" должны быть заранее выставлены лимитные заявки на покупку всего объёма продаваемых акций.
Очевидно, что это — весьма маловероятное событие.
Наконец, как правило у брокера в API есть ограничение на объём в одной торговой операции, которое значительно меньше 4294967196.
Но — зачем все эти тонкости, если можно просто ляпнуть о банкротстве?
Вместо всего этого, насколько я понимаю, была ссылка на авторитет.
Однако, авторитетов не существует, и каждый раз необходимо обосновывать, а не ссылаться на свой или чужой авторитет, что это кто-то авторитетный сказал, потому что каким бы ни был авторитетным человек, он может ошибиться.
Бесполезно продавать Rust, тем более, таким способом.
Почему не было даже попыток применить данное решение?
Потому что вам теперь надо обновлять обе сигнатуры, и компилятор далеко не всегда напомнит вам это сделать.
Потому что теперь вы не можете взять адрес sendOrder
(по крайней мере, в контекстах вроде аргумента для стандартных алгоритмов, где ожидаемый тип callable не фиксирован).
Потому что если вы захотите принимать double
или float
последним аргументом, то вам уже придётся обмазываться концептами и писать что-то вроде requires (!(std::is_same_v<T3, double> || std::is_same_v<T3, float>)
). Очень удобно и дружественно к людям, в чьи обязанности входит не только штудирование талмуда++. Кстати, тут крошка-джун пришёл к отцу, и спросила кроха: «папа, а что тут лучше, std::same_as
или std::is_same_v
? Какая вообще между ними разница? Какой-какой частичный порядок? Я полностью каждый день порядок навожу!»
следовало бы научиться пользоваться C++
И решить проблему останова на сдачу.
Человек, утверждающий подобное, даже не пытался изучить предмет.
Человек, отвечающий подробное, даже не понимает смысла учебных, иллюстрирующих примеров.
Knight capital, кстати, передаёт привет.
Потому что вам теперь надо обновлять обе сигнатуры, и компилятор далеко не всегда напомнит вам это сделать.
Зачем их обновлять?
Если добавляются перегрузки с другим числом параметров, — да, ещё шаблоны придётся добавлять.
Потому что теперь вы не можете взять адрес
sendOrder
(по крайней мере, в контекстах вроде аргумента для стандартных алгоритмов, где ожидаемый тип callable не фиксирован).
Если имеются перегрузки, это и так невозможно без фиксации ожидаемого типа.
И это — правда, та функция, адрес которой, весьма вероятно, необходимо будет брать?
Потому что если вы захотите принимать
double
илиfloat
последним аргументом
Сейчас там и так double
последним аргументом.
В таких местах float
не используется.
И решить проблему останова на сдачу.
В данной задаче это — необходимо?
По-моему, вы выдумываете несуществующие трудности.
Человек, отвечающий подробное, даже не понимает смысла учебных, иллюстрирующих примеров.
Не понял мысли.
Knight capital, кстати, передаёт привет.
Вы, видимо, обознались.
Я встаплюсь за иу, хоть он человек специфический, за что многие здесь на хабре его и любят, но в этом случае, имхо, он прав чуть более чем полностью.
Начну с конца, кнайт капитал это не догадка о том откуда вы узнали про бизнес логику биржевых торгов, а ключевая фраза для самостоятельного гугления эпического эпик фэйла на плюсах, как раз из области трэйдинга.
Отсылка к учебному примеру означает что в реальном коде будет очень много отвлекающих ньюансов которые со 100% вас успешно отвлекут от сути проблемы, поэтому нельзя винить человека в незнании чего то лишь потому что вы с лёгкостью обошли проблему учебного примера.
Отсылка к проблеме остонова означает оценку сложности такой "простой" задачи как "пойди уже и выучи наконец плюсы" как равную == задача невыполнима в принципе.
В пункте про дабл или флот, ударение стоит на ИЛИ. Я не уверен конечно, но подозреваю что сейчас вероятно передать флоат там где у вас сигнатура ожидает дабл не удастся, хотя выглядит безопасно, разумно и удобно, но я не проверял. В любом случае если ваша сигнатура требует принимать несколько типов в ТОМ же самом параметре, которые из коробки могут друг в друга преобразовываться, то придется обмазываться концептами.
Пункт про обновление сигнатуры это когда вы решили добавить ещё один параметр и сделали это только в реальной функции, но забыли (или если джун, то даже и не предполагали что надо) обновить темплэйт делет, чтобы учесть новый параметр.
эпического эпик фэйла на плюсах,
Извините, но переиспользование feature flag приведёт к проблемам вне зависимости от того, на чём вы пишете. Хоть на Агде.
Начну с конца, кгайт капитал это не догадка о том откуда вы узнали про бизнес логику биржевых торгов, а ключевая фраза для самостоятельного гугления эпического эпик фэйла на плюсах, как раз из области трэйдинга.
А вы сами — в курсе, что там произошло, и при чём здесь, вообще, C++?
И в наше время Google'ить такие вещи — неэффективно, особенно, если необходимо понять суть, а не поверить в чей-то пересказ через 10-ые руки.
Отсылка к учебному примеру означает что в реальном коде будет очень много отвлекающих ньюансов которые со 100% вас успешно отвлекут от сути проблемы, поэтому нельзя винить человека в незнании чего то лишь потому что вы с лёгкостью обошли проблему учебного примера.
Учебный он или нет — не важно.
Имеющуюся проблему не пытались решить по-настоящему.
Для этого не требуется знать C++ досконально.
И это — не какая-то редко используемая тонкость редко используемого механизма.
Отсылка к проблеме остонова означает оценку сложности такой "простой" задачи как "пойди уже и выучи наконец плюсы" как равную == задача невыполнима в принципе.
Это — несопоставимые по сложности задачи, C++ выучить значительно проще, тем более, что для решения данной задачи не требуется его учить досконально.
В пункте про дабл или флот, ударение стоит на ИЛИ. Я не уверен конечно, но подозреваю что сейчас вероятно передать флоат там где у вас сигнатура ожидает дабл не удастся, хотя выглядит безопасно, разумно и удобно, но я не проверял. В любом случае если ваша сигнатура требует принимать несколько типов в ТОМ же самом параметре, которые из коробки могут друг в друга преобразовываться, то придется обмазываться концептами.
Пункт про обновление сигнатуры это когда вы решили добавить ещё один параметр и сделали это только в реальной функции, но забыли (или если джун, то даже и не предполагали что надо) обновить темплэйт делет, чтобы учесть новый параметр.
По поводу добавления параметров как для единственной функции, так и для перегруженных функций: можно применить parameter pack в шаблоне.
Примерно, так:
#include <cstdlib>
#include <iostream>
template <typename... R>
void sendOrder(R...) = delete;
void sendOrder(char const *symbol,
bool buy,
unsigned quantity,
double price)
{
std::cout << symbol << ' '
<< std::boolalpha << buy << ' '
<< quantity << ' '
<< price << std::endl;
}
void sendOrder(char const *symbol,
bool buy,
unsigned quantity,
double price,
double slippage)
{
std::cout << symbol << ' '
<< std::boolalpha << buy << ' '
<< quantity << ' '
<< price << ' '
<< slippage << std::endl;
}
int main() {
sendOrder("DAL", false, 10u, 5.0);
sendOrder("DAL", false, 10u, 5.0, 3.0);
#if 0
sendOrder("DAL", false, 10u, 5);
sendOrder("DAL", false, 10u, 5.0, 3);
#endif
return EXIT_SUCCESS;
}
Теперь единственный шаблон "обслуживает" все перегрузки функции sendOrder
.
Его не требуется обновлять.
По поводу double
или float
: что значит "обмазываться"?
Применять?
Можно и применить, например:
#include <cstdlib>
#include <iostream>
#include <type_traits>
#include <concepts>
template <typename... R>
auto sendOrder(R...) = delete;
void sendOrder(const char *symbol,
bool buy,
unsigned quantity,
double price)
{
std::cout << symbol << ' '
<< std::boolalpha << buy << ' '
<< quantity << ' '
<< price << std::endl;
}
void sendOrder(const char *symbol,
bool buy,
unsigned quantity,
double price,
double slippage) {
std::cout << symbol << ' '
<< std::boolalpha << buy << ' '
<< quantity << ' '
<< price << ' '
<< slippage << std::endl;
}
void sendOrder(const char *symbol,
bool buy,
unsigned quantity,
double price,
double slippage,
double stoploss) {
std::cout << symbol << ' '
<< std::boolalpha << buy << ' '
<< quantity << ' '
<< price << ' '
<< slippage << ' '
<< stoploss << std::endl;
}
void sendOrder(const char *symbol,
bool buy,
unsigned quantity,
double price,
double slippage,
double stoploss,
double takeprofit) {
std::cout << symbol << ' '
<< std::boolalpha << buy << ' '
<< quantity << ' '
<< price << ' '
<< slippage << ' '
<< stoploss << ' '
<< takeprofit << std::endl;
}
template <typename T>
concept FloatOrDouble = std::same_as<T, double> || std::same_as<T, float>;
template <typename... Ts>
concept HasFloat = (std::same_as<Ts, float> || ...);
template <typename... Ts>
constexpr bool is_enabled() {
return sizeof...(Ts) > 0 &&
(FloatOrDouble<Ts> && ...) &&
HasFloat<Ts...>;
}
template <typename... R>
std::enable_if_t<is_enabled<R...>()>
sendOrder(const char *symbol, bool buy, unsigned quantity, R... r) {
return sendOrder(symbol, buy, quantity, static_cast<double>(r)...);
}
int main() {
sendOrder("DAL", false, 10u, 5.0);
sendOrder("DAL", false, 10u, 4.0f);
std::cout << '\n';
sendOrder("DAL", false, 10u, 5.0, 3.0);
sendOrder("DAL", false, 10u, 5.0, 2.0f);
sendOrder("DAL", false, 10u, 4.0f, 3.0);
sendOrder("DAL", false, 10u, 4.0f, 2.0f);
std::cout << '\n';
sendOrder("DAL", false, 10u, 5.0, 3.0, 7.0);
sendOrder("DAL", false, 10u, 4.0f, 2.0f, 6.0f);
sendOrder("DAL", false, 10u, 4.0f, 3.0, 6.0f);
std::cout << '\n';
sendOrder("DAL", false, 10u, 5.0, 3.0, 7.0, 9.0);
sendOrder("DAL", false, 10u, 4.0f, 2.0f, 6.0f, 8.0f);
sendOrder("DAL", false, 10u, 4.0f, 3.0, 6.0f, 9.0);
sendOrder("DAL", false, 10u, 5.0, 2.0f, 7.0, 8.0f);
#if 0
sendOrder("DAL", false, 10u, 5);
sendOrder("DAL", false, 10u, 5, 3.0);
sendOrder("DAL", false, 10u, 5.0, 3);
sendOrder("DAL", false, 10u, 4.0f, 3);
sendOrder("DAL", false, 10u, 4.0f, 3, 6.0f);
sendOrder("DAL", false, 10u, 5.0, 2.0f, 7, 8.0f);
#endif
return EXIT_SUCCESS;
}
Один шаблон с parameter pack'ом, который "запрещает приведения".
Затем ещё один перегруженный шаблон с использованием SFINAE выступает обёрткой, но только для тех вызовов, в которых используется хотя бы один float
.
Перегрузок много, а шаблон функции-обёртки — один.
Наверное, можно ещё что-нибудь придумать на эту тему, но для этого необходимо забыть про Rust, и начать хотеть найти решение, а не искать оправдания, почему решение искать не сто́ит.
Зачем их обновлять?
Потому что код меняется. Вчера const char*
было, сегодня std::string_view
(или наоборот). Вчера double price
, сегодня fixed_point<4> price
(в который неплохо бы конвертировать из литералов, кстати, но не будем вскрывать эту тему, перегрузки разбегаются как тараканы).
Вот я скопипастил ваш код (и заодно добавил requires
из соседнего поинта) и сделал вид, что он эволюционирует со временем, «забыв» поменять тип первого аргумента в удалённой перегрузке:
template <typename T1, typename T2, typename T3>
requires (!(std::same_as<T3, double> || std::same_as<T3, float>))
void sendOrder(std::string_view symbol, T1 buy, T2 quantity, T3 price) = delete;
void sendOrder(const char *symbol, bool buy, unsigned quantity, double price)
{
}
int main()
{
sendOrder("DAL", true, -10, 5.0);
}
Обратите внимание на -10
. Задача со звёздочкой: скомпилится или нет? (ответ: да, скомпилится)
Теперь я убираю requires
. Скомпилится или нет?
clang говорит «ambiguous» и ошибка, gcc компилирует (лан, моя вина — надо -pedantic
добавлять), но говорит
warning: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second:
13 | sendOrder("DAL", true, -10, 5.0);
| ~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~
Что за worst conversion? Откуда? Сможете сходу огласить весь список conversion'ов? Почему requires
так влияет? Смогли бы предсказать такой результат, просто глядя на код? Сможете объяснить среднему программисту на C++, почему результат такой и причём тут requires
? Сможете объяснить среднему трейдеру, который на C++ пишет постольку, поскольку вы ему даёте апишку для вашего инфра-кода, а у себя он там обмазывается q, матлабами и прочим?
Сможете быть уверенным, что сможете адекватно поддерживать кодовую базу на миллион-другой строк, где подобными финтами всё обмазано, и где простейшее изменение рискует превратиться в сутки-другие расхлёбывания выблева компилятора минимум, и где рекомпиляция одного TU занимает минуты даже в дебаг-билде, без оптимизаций? Мне приходилось поддерживать, не рекомендую.
И это — правда, та функция, адрес которой, весьма вероятно, необходимо будет брать?
Да, а что? std::bind_front(&sendOrder, "GOOG");
Сейчас там и так
double
последним аргументом.
В таких местахfloat
не используется.
Почему? Я хочу, чтобы у меня в кэшлайн помещалось 16 цен, а не 8 — почему вы мне запрещаете так делать?
Олсо, в таких местах и double
не используется, а используется фиксированная точка, но неважно — это учебный пример.
В данной задаче это — необходимо?
Не, просто задача «освоить C++» примерно настолько же реализуема, как «решить проблему останова».
Вы, видимо, обознались.
Вовсе нет. Просто считать, что это число вот прям сразу пойдёт в пакетик на биржу — это несколько ошибочно. Там, например, может быть что-то по смыслу как
void sendOrder(...)
{
const int batchSize = computeSmallBatchSize(quantity);
for (int i = 0; i < quantity; i += batchSize) {
sendToExch(std::min(batchSize, quantity - i));
waitPriceToSettle();
}
}
И если вы хотите купить 10 акций, а случайно покупаете -10 = 4294967286, то до того, как у вас сработают всякие риск-чеки, дневные лимиты, и прочее, у вас уже уйдёт ордеров на сотню тыщ акций.
Все же, прежде чем падать в занудство, стоит просто признать, что задача имеет решение в С++, хоть и неудобное =)
Но такое неудобство не стоит смены языка.
Зы, кстати про delete в шаблонах не знал. Никто не знает С++ (с)
Зы, кстати про delete в шаблонах не знал.
Это предполагается по "философии" C++.
Если что-то применимо к функции, то логично предположить, что это применимо и к методам, и к шаблонам функций/методов, а в некоторых случаях — и к лямбдам.
Это не всегда так, потому что могут быть препятствующие тому причины, но по "философии" C++ логично это предположить.
Никто не знает С++ (с)
Это — верно, но "философия" C++, а также внутреннее ощущение от свойств механизмов C++ часто выручает.
Именно внутренне ощущение от механизма = delete
натолкнуло меня на мысль о применении его здесь.
Вот я скопипастил ваш код (и заодно добавил
requires
из соседнего поинта) и сделал вид, что он эволюционирует со временем, «забыв» поменять тип первого аргумента в удалённой перегрузке:
С таким подходом проблемы будут независимо от языка.
Сможете объяснить среднему трейдеру, который на C++ пишет постольку, поскольку вы ему даёте апишку для вашего инфра-кода, а у себя он там обмазывается q, матлабами и прочим?
Наверное, это не его забота, обеспечить приёмистость не точно соответствующих типов в предоставляемом API, — но только таких, которые разрешены проектировщиком API, а не любых, которые язык позволяет.
Есть предложенный механизм, и если хотеть добиться результата, этот механизм можно использовать для достижения этого самого результата.
Сможете быть уверенным, что сможете адекватно поддерживать кодовую базу на миллион-другой строк, где подобными финтами всё обмазано, и где простейшее изменение рискует превратиться в сутки-другие расхлёбывания выблева компилятора минимум, и где рекомпиляция одного TU занимает минуты даже в дебаг-билде, без оптимизаций?
Если не продумывать каждое решение, а пытаться наворачивать сверху, да побыстрее, а также быть вынужденным постоянно что-то менять, — да проблемы будут.
Да, а что?
std::bind_front(&sendOrder, "GOOG");
Эта проблема не является специфичной для данного случая.
Придётся приводить к одной из перегрузок тем или иным способом или указывать явно параметр шаблона.
И если хочется, чтобы это работало, как будто перегрузка теперь действует для тех же функций, но без первого параметра, который теперь фиксирован, и который не требуется передавать, придётся думать, как создать такое решение.
Почему? Я хочу, чтобы у меня в кэшлайн помещалось 16 цен, а не 8 — почему вы мне запрещаете так делать?
Я не запрещаю, это — просто наблюдение.
Не, просто задача «освоить C++» примерно настолько же реализуема, как «решить проблему останова».
Всё же, первая задача значительно проще, вы преувеличиваете.
Правда, это не значит, что первую задачу можно досконально решить.
Но досконально и не требуется.
Вовсе нет. Просто считать, что это число вот прям сразу пойдёт в пакетик на биржу — это несколько ошибочно.
Это, по всей видимости, не имеет отношения к реально происходившим событиям, потому что сделка была не одна большая, а — большое количество относительно мелких.
И если вы хотите купить 10 акций, а случайно покупаете -10 = 4294967286, то до того, как у вас сработают всякие риск-чеки, дневные лимиты, и прочее, у вас уже уйдёт ордеров на сотню тыщ акций.
Нет, сработает проверка на максимальный объём в одной сделке, и вернётся ошибка, ничего никуда не уйдёт.
И если бы ушло, то ордер был бы один.
Ещё код по теме можно посмотреть в моём ответе @boldapeу здесь.
С таким подходом проблемы будут независимо от языка.
С каким — таким? «Посмотреть предложенный собеседником код и найти там проблемы»?
Проблемы с предложенным вами подходом потому, что он нелокален. Ну, вернее, это проблема не вашего подхода, а общей философии C++: например, если вы почитаете или послушаете гугловского Титуса Винтерса, топящего за что-то вроде «C++ design unit is an overload set» (а не функция или класс или модуль или…), и послушаете про его же кулстори про эволюцию гугловской кодовой базы, то увидите, что добрая часть проблем вылезает именно из-за этой нелокальности. Семантика, скрывающаяся за именем, оказывается размазана не по одному определению одной функции за этим именем, а по всему множеству всех функций с этим именем, потенциально в разных неймспейсах (привет ADL), и изменение этого множества приводит к забавным нелокальным спецэффектам (например, вопрос о роли requires
вы почему-то пропустили — но я вас понимаю, сам бы его пропустил). Это — один из худших видов coupling'а.
Наверное, это не его забота, обеспечить приёмистость не точно соответствующих типов в предоставляемом API, — но только таких, которые разрешены проектировщиком API, а не любых, которые язык позволяет.
Наверное. Моей заботой (и вообще обязанностью) как программиста инфры было именно обеспечить максимально комфортную жизнь трейдерам, которые в статистике и прочих своих этих всех альфах-бетах разбираются куда лучше, чем в C++.
Есть предложенный механизм, и если хотеть добиться результата, этот механизм можно использовать для достижения этого самого результата.
Я с этим не спорю. Речь о другом: у предложенного механизма есть много негативных аспектов, поэтому говорить, что он эквивалентен решениям из раста (или из хаскеля — я так-то раст не люблю и хаскелист вообще), несколько нечестно.
Если не продумывать каждое решение, а пытаться наворачивать сверху, да побыстрее, а также быть вынужденным постоянно что-то менять, — да проблемы будут.
Как успехи с продумыванием кодовой базы на года вперёд?
Если у вас достаточно времени, то проще на том же хаскеле наваять для тех же трейдеров DSL с понятными сообщениями об ошибках, понятным поведением, понятным предметно-специфичным статическим анализом, и предсказуемой кодогенерацией. Да, проще, чем заставлять всех разбираться в экспоненциальной сложности C++.
Эта проблема не является специфичной для данного случая.
Ну, да. Но в этом случае она вылезает тоже.
Всё же, первая задача значительно проще, вы преувеличиваете.
Observationally equal. Я не знаю ни одного человека, решившего задачу останова, и не знаю ни одного человека, который бы знал C++. И, кстати, сверхвысокие HFT-шные зарплаты не позволяют их найти — все знатоки C++ в основном почему-то прячутся в комментариях на хабре или опеннете и тому подобных, вместо того, чтобы приходить на собеседования и получать свои заслуженные 500-700 в год.
Это, по всей видимости, не имеет отношения к реально происходившим событиям, потому что сделка была не одна большая, а — большое количество относительно мелких.
И если бы ушло, то ордер был бы один.
Knight capital — это так, намёк на то, что при факапах в коде проверки и ограничители срабатывают не сразу, и даже их может оказаться недостаточно (они ж обанкротились и закрылись).
Так в коде выше на биржу уходит тоже большое число относительно мелких (если хочется, можете вместо sendOrder
читать splitAndExecuteOrder
или чё-т такое, сути это не меняет). Или вы думаете, что если вам надо купить/продать 100500 акций, то вы их покупаете одним пакетом? Это плохая идея по куче причин.
Ещё код по теме можно посмотреть в моём ответе
Ожидать, что люди будут в продакшене писать и поддерживать такой код — несерьёзно.
Если у вас достаточно времени, то проще на том же хаскеле наваять для тех же трейдеров DSL с понятными сообщениями об ошибках, понятным поведением, понятным предметно-специфичным статическим анализом, и предсказуемой кодогенерацией. Да, проще, чем заставлять всех разбираться в экспоненциальной сложности C++.
И все равно у одного из них моделька однажды дернется и скажет, что нужно 429496728 акций купить (здесь на десятичный порядок меньше, чем числа, которые можно интерпретировать, как возможно signed, и оттого все куда хуже). Не от того защищаемся и не так.
Так в коде выше на биржу уходит тоже большое число относительно мелких (если хочется, можете вместо
sendOrder
читатьsplitAndExecuteOrder
или чё-т такое, сути это не меняет). Или вы думаете, что если вам надо купить/продать 100500 акций, то вы их покупаете одним пакетом? Это плохая идея по куче причин.
А это ужасная идея по всем причинам. Вы не будете безусловно дробить операции о крупных сделках без обратной связи о цене в любом случае. Потому что никакой задачи, кроме как заспуфить биржевой движок (за кучу ваших денег) это не решает в принципе.
все знатоки C++ в основном почему-то прячутся в комментариях на хабре или опеннете и тому подобных, вместо того, чтобы приходить на собеседования и получать свои заслуженные 500-700 в год.
Так потому что это не о знании C++, не о умении писать торговые стратегии, не о performance, и судя, по NDA-собеседованиям, и личному опыту, честно говоря, вообще не пойми о чем. Долго уже мечтаю увидеть статью о том, почему HFT не миф, и в каких конкретных кейсах до оптимизации на одну наносекунду винрейт был 50%, а после - 50.001%. Такого матераила нет, понимания почему оно должно работать в принципе - тоже нет. Много чего в трейдинге реально работает, но HFT, как вижу, не более, чем об не совсем заслуженных n-m сравнимого порядка в год.
И все равно у одного из них моделька однажды дернется и скажет, что нужно 429496728 акций купить (здесь на десятичный порядок меньше, чем числа, которые можно интерпретировать, как возможно signed, и оттого все куда хуже).
Только в одном случае матожидание времени до наступления этого события — сто лет, а в другом — скажем, месяц. Что выберете?
А это ужасная идея по всем причинам. Вы не будете безусловно дробить операции о крупных сделках без обратной связи о цене в любом случае.
Вы хотите, чтобы я тут биржевой движок целиком написал в комментариях, что ли? Моя цель — проиллюстрировать, считайте, полупсевдокодом, почему «решение купить (uint)-10
акций» не обязано вот прям сразу стриггерить лимиты.
Долго уже мечтаю увидеть статью о том, почему HFT не миф, и в каких конкретных кейсах до оптимизации на одну наносекунду винрейт был 50%, а после - 50.001%.
Это всё на самом деле неважно: есть предложение по деньгам, есть требование определённых знаний, и как-то вот с людьми, этим требованиям удовлетворяющими целиком, как-то трудно.
Как же надоели эти псевдозаумные высказывания о высоконагруженных приложениях,... А по факту на питоне, от нубов.
/оффтоп офф
А кому надо, тот просто работает. И не распространяется
Только в одном случае матожидание времени до наступления этого события — сто лет, а в другом — скажем, месяц. Что выберете?
Выберу ничего из этого, разумеется, жертвуя неким количеством наносекунд ой микросекунд да ну миллисекунд, нет попыткой входа в сделку в принципе. Мы же не биржевой движок пишем, а алгоритм, который с ним взаимодействует, у нас всегда есть ограничения и на каждом шаге невообразимая халатность их не проверять.
Вы хотите, чтобы я тут биржевой движок целиком написал в комментариях, что ли? Моя цель — проиллюстрировать, считайте, полупсевдокодом, почему «решение купить
(uint)-10
акций» не обязано вот прям сразу стриггерить лимиты.
Вот а если уже биржевой движок писать, то корректность работы и ее доказательная база на совести разработчиков, но снова, упс, не сэкономленные наносекунды. Не видел ни одного реального кейса где бы биржевые движки за эти наносекунды соревновались на неком конкурентном рынке. Его нету.
Это всё на самом деле неважно: есть предложение по деньгам, есть требование определённых знаний, и как-то вот с людьми, этим требованиям удовлетворяющими целиком, как-то трудно.
Для многих людей все же важно понимать, что и зачем они делают, иначе возникает много лишних вопросов с тем "как" и "что после этого будет". Вот этой формализации не видел в этой сфере, увы.
Мы же не биржевой движок пишем, а алгоритм, который с ним взаимодействует, у нас всегда есть ограничения и на каждом шаге невообразимая халатность их не проверять.
Не понял логики, извините. Почему алгоритм, с ним взаимодействующий, не имеет требований по латентности?
Может, мы разные вещи понимаем под этими алгоритмами, разве что.
Вот а если уже биржевой движок писать, то корректность работы и ее доказательная база на совести разработчиков, но снова, упс, не сэкономленные наносекунды. Не видел ни одного реального кейса где бы биржевые движки за эти наносекунды соревновались на неком конкурентном рынке. Его нету.
А я видел, и прямо участвовал, и не раз имел, как их называл мой тогдашний менеджер/тимлид/чиф текникал офисер, «проекты» на неделю-две по ускорению отдельной пердюшки (вроде mycompany::circular_buffer<T>::insert
) на 10-50 нс (в смысле, 10-50 нс уже считалось хорошим результатом, и люди были готовы платить за это зарплату и радовались этим самым результатам).
Для многих людей все же важно понимать, что и зачем они делают, иначе возникает много лишних вопросов с тем "как" и "что после этого будет".
Если не ограничивать искусственно глубину рекурсии в задавалке этих вопросов, то довольно быстро вы поймёте, что эти вопросы не имеют смысла. Важно только то, идёт ли прямой эффект (остальные всё равно не просчитаете) вашей работы в плюс или в минус согласно вашей системе ценностей. Поэтому, например, я не пошёл в Anduril (хотя там прямо стык моих самых сильных областей — и машинное обучение, и хаскель, и формальные методы, и немножк плюсов для совсем лоу-левела, да и некоторые из основателей там весьма базированные чуваки), тогда как трейдинг — это где-то нейтрально.
С каким — таким? «Посмотреть предложенный собеседником код и найти там проблемы»?
Нет, с подходом "забуду здесь то", "забуду там это".
Проблемы с предложенным вами подходом потому, что он нелокален. Ну, вернее, это проблема не вашего подхода, а общей философии C++: например, если вы почитаете или послушаете гугловского Титуса Винтерса, топящего за что-то вроде «C++ design unit is an overload set» (а не функция или класс или модуль или…), и послушаете про его же кулстори про эволюцию гугловской кодовой базы, то увидите, что добрая часть проблем вылезает именно из-за этой нелокальности. Семантика, скрывающаяся за именем, оказывается размазана не по одному определению одной функции за этим именем, а по всему множеству всех функций с этим именем, потенциально в разных неймспейсах
Да, проблема имеется, но это лишь значит, что следует стараться её обходить и избегать.
Моей заботой (и вообще обязанностью) как программиста инфры было именно обеспечить максимально комфортную жизнь трейдерам, которые в статистике и прочих своих этих всех альфах-бетах разбираются куда лучше, чем в C++.
Трейдерам следует программировать на C++ только в одном случае: если они, в первую очередь, программисты на C++, а трейдеры они — уже по совместительству.
В остальных случаях это похоже на попытку бриться топором.
Одна ошибка — и весьма серьёзные последствия не заставят себя ждать.
У трейдеров должен быть другой язык программирования.
Речь о другом: у предложенного механизма есть много негативных аспектов, поэтому говорить, что он эквивалентен решениям из раста (или из хаскеля — я так-то раст не люблю и хаскелист вообще), несколько нечестно.
Я и не говорил, что он эквивалентен.
Я говорил, что он в C++ — есть, а в статье неявно подразумевается, что — нет.
Как успехи с продумыванием кодовой базы на года вперёд?
Это — не панацея, но если этим не заниматься, будет значительно хуже.
Ну, да. Но в этом случае она вылезает тоже.
Если не думать, то проблему не решить в 100% случаев.
Если же думать, то процент начинает ощутимо отличаться от 100.
Я решил проблему, связанную с тем, что функции `sendOrder` — перегруженные, и поэтому механизм std::bind_first
не работает.
Удалось отложить инстанцирование и, соответственно, разрешение перегрузки, которая теперь происходит не в момент вызова bind_first
, а в момент вызова того, что вернула bind_first
(добавился шаблон класса SendOrder
и шаблон функции bind_first
, и этот шаблон в качестве первого параметра принимает шаблон класса, а не тип или значение):
#include <cstdlib>
#include <iostream>
#include <type_traits>
#include <concepts>
template <typename... R>
auto sendOrder(R...) = delete;
void sendOrder(const char *symbol,
bool buy,
unsigned quantity,
double price)
{
std::cout << symbol << ' '
<< std::boolalpha << buy << ' '
<< quantity << ' '
<< price << std::endl;
}
void sendOrder(const char *symbol,
bool buy,
unsigned quantity,
double price,
double slippage) {
std::cout << symbol << ' '
<< std::boolalpha << buy << ' '
<< quantity << ' '
<< price << ' '
<< slippage << std::endl;
}
void sendOrder(const char *symbol,
bool buy,
unsigned quantity,
double price,
double slippage,
double stoploss) {
std::cout << symbol << ' '
<< std::boolalpha << buy << ' '
<< quantity << ' '
<< price << ' '
<< slippage << ' '
<< stoploss << std::endl;
}
void sendOrder(const char *symbol,
bool buy,
unsigned quantity,
double price,
double slippage,
double stoploss,
double takeprofit) {
std::cout << symbol << ' '
<< std::boolalpha << buy << ' '
<< quantity << ' '
<< price << ' '
<< slippage << ' '
<< stoploss << ' '
<< takeprofit << std::endl;
}
template <typename T>
concept FloatOrDouble = std::same_as<T, double> || std::same_as<T, float>;
template <typename... Ts>
concept HasFloat = (std::same_as<Ts, float> || ...);
template <typename... Ts>
constexpr bool is_enabled() {
return sizeof...(Ts) > 0 &&
(FloatOrDouble<Ts> && ...) &&
HasFloat<Ts...>;
}
template <typename... R>
std::enable_if_t<is_enabled<R...>()>
sendOrder(const char *symbol, bool buy, unsigned quantity, R... r) {
return sendOrder(symbol, buy, quantity, static_cast<double>(r)...);
}
template <typename... R>
struct SendOrder {
void operator ()(R &&... r) {
sendOrder(std::forward<R>(r)...);
}
};
template <template <typename...> typename F, typename... FST>
auto
bind_first(FST &&... fst) {
return [&]<typename... LST>(LST &&... lst) {
F<FST..., LST...>{}(std::forward<FST>(fst)..., std::forward<LST>(lst)...);
};
}
int main() {
sendOrder("DAL", false, 10u, 5.0);
sendOrder("DAL", false, 10u, 4.0f);
std::cout << '\n';
sendOrder("DAL", false, 10u, 5.0, 3.0);
sendOrder("DAL", false, 10u, 5.0, 2.0f);
sendOrder("DAL", false, 10u, 4.0f, 3.0);
sendOrder("DAL", false, 10u, 4.0f, 2.0f);
std::cout << '\n';
sendOrder("DAL", false, 10u, 5.0, 3.0, 7.0);
sendOrder("DAL", false, 10u, 4.0f, 2.0f, 6.0f);
sendOrder("DAL", false, 10u, 4.0f, 3.0, 6.0f);
std::cout << '\n';
sendOrder("DAL", false, 10u, 5.0, 3.0, 7.0, 9.0);
sendOrder("DAL", false, 10u, 4.0f, 2.0f, 6.0f, 8.0f);
sendOrder("DAL", false, 10u, 4.0f, 3.0, 6.0f, 9.0);
sendOrder("DAL", false, 10u, 5.0, 2.0f, 7.0, 8.0f);
std::cout << '\n';
auto const sendOrderGoog{bind_first<SendOrder>("Goog")};
sendOrderGoog(false, 10u, 5.0);
sendOrderGoog(false, 10u, 4.0f);
std::cout << '\n';
sendOrderGoog(false, 10u, 5.0, 3.0);
sendOrderGoog(false, 10u, 5.0, 2.0f);
sendOrderGoog(false, 10u, 4.0f, 3.0);
sendOrderGoog(false, 10u, 4.0f, 2.0f);
std::cout << '\n';
sendOrderGoog(false, 10u, 5.0, 3.0, 7.0);
sendOrderGoog(false, 10u, 4.0f, 2.0f, 6.0f);
sendOrderGoog(false, 10u, 4.0f, 3.0, 6.0f);
std::cout << '\n';
sendOrderGoog(false, 10u, 5.0, 3.0, 7.0, 9.0);
sendOrderGoog(false, 10u, 4.0f, 2.0f, 6.0f, 8.0f);
sendOrderGoog(false, 10u, 4.0f, 3.0, 6.0f, 9.0);
sendOrderGoog(false, 10u, 5.0, 2.0f, 7.0, 8.0f);
#if 0
sendOrder("DAL", false, 10u, 5);
sendOrder("DAL", false, 10u, 5, 3.0);
sendOrder("DAL", false, 10u, 5.0, 3);
sendOrder("DAL", false, 10u, 4.0f, 3);
sendOrder("DAL", false, 10u, 4.0f, 3, 6.1f);
sendOrder("DAL", false, 10u, 5.0, 2.0f, 7, 8.0f);
sendOrderGoog(false, 10u, 5);
sendOrderGoog(false, 10u, 5, 3.0);
sendOrderGoog(false, 10u, 5.0, 3);
sendOrderGoog(false, 10u, 4.0f, 3);
sendOrderGoog(false, 10u, 4.0f, 3, 6.0f);
sendOrderGoog(false, 10u, 5.0, 2.0f, 7, 8.0f);
#endif
return EXIT_SUCCESS;
}
При вызове bind_first
необходимо явно указывать первый параметр шаблона.
Класс SendOrder
— один на все перегрузки sendOrder
.
Я не знаю ни одного человека, решившего задачу останова, и не знаю ни одного человека, который бы знал C++.
Эти данные — слишком скудные, чтобы делать выводы по данному вопросу.
Knight capital — это так, намёк на то, что при факапах в коде проверки и ограничители срабатывают не сразу, и даже их может оказаться недостаточно (они ж обанкротились и закрылись).
Так там не в ограничениях дело было.
Он же не пытались купить или продать сразу 2^32 акций.
Так в коде выше на биржу уходит тоже большое число относительно мелких (если хочется, можете вместо
sendOrder
читатьsplitAndExecuteOrder
или чё-т такое, сути это не меняет).
Если оно уходит без предварительной проверки достаточности средств, то это — вопиющая некомпетентность брокера, потому что шортятся акции под залог, и следует проверить его размер, иначе у брокера будут проблемы, если с трейдера нечего взять, а цена резко улетит вверх.
И мне почему-то кажется, что долго такой брокер не "проживёт".
Тем более, что если купить без плеча на N денег акций, то потерять, в худшем случае, можно не более N денег.
А зашортив акций на N денег, потерять можно неограниченное количество денег. Например, если после шорта акция выросла в 15 раз в цене, потери составят 15 * N денег.
И реальные случаи такие уже были.
Или вы думаете, что если вам надо купить/продать 100500 акций, то вы их покупаете одним пакетом? Это плохая идея по куче причин.
Критикуя статью, в частности, банкротство при отправке на продажу 2^32 акций, я подробно описывал детали продажи такого количества акций, откуда должно быть понятно, что в базовых вещах, связанных с биржевой торговлей, я разбираюсь весьма неплохо.
Ожидать, что люди будут в продакшене писать и поддерживать такой код — несерьёзно.
Дело в том, что это — код, демонстрирующий идею и её работоспособность, а не код для production'а.
Не знаю, почему вы восприняли его как код для production'а.
Нет, с подходом "забуду здесь то", "забуду там это".
А именно так ведь на практике всё и происходит. Ваш коллега написал какой-то код, вы через полгода его меняете. Вы не то что «забыли здесь то», вы этого и не помнили никогда. Или вы ожидаете, что каждый контрибьютор в кодовую базу будет следить за всеми изменениями и поддерживать ментальный контекст каждого другого контрибьютора? Или даже если вы сами написали — через полгода вы уже забудете такие нелокальные финты.
Поэтому лично я признаю, что память у меня ограничена, и диапазон внимания ограничен, и код я пишу не всегда в состоянии максимальной концентрации, поэтому единственный выход — доверить тайпчекеру проверки, что я не делаю ерунды. И именно поэтому я люблю строгие системы типов, а подобные не ловящиеся компилятором вещи — не люблю. И, опять же, именно поэтому
Это — не панацея, но если этим не заниматься, будет значительно хуже.
ключ не в планировании на года вперёд, а в написании кода так, чтобы при его изменении компилятор вас тыкнул носом в максимальное количество мест, где ломаются предположения из-за ваших свежих изменений.
Да, проблема имеется, но это лишь значит, что следует стараться её обходить и избегать.
А обсуждаемый подход её только усиливает.
Трейдерам следует программировать на C++ только в одном случае: если они, в первую очередь, программисты на C++, а трейдеры они — уже по совместительству.
У трейдеров должен быть другой язык программирования.
Какой бы вы предложили?
Но да, именно поэтому я делал DSL для трейдеров. До этого был eDSL на плюсах, embedded именно в плюсовые темплейты. Компилировалось всё в довольно эффективный код, но понять это было, скажем так, проблематично.
Я говорил, что он в C++ — есть, а в статье неявно подразумевается, что — нет.
ИМХО лучшая статья на хабре — условие как компромисс. Какие-то границы на предлагаемые подходы, сохраняющие эту нечёткую эквивалентность, в таких обсуждениях есть: например, на юзабельность такого подхода в проде, или на количество кода, которое нужно для его поддержки написать и поддерживать, и так далее.
А то так-то понятно, что и на сишке можно сделать аналог перегрузок и шаблонов (например, написав на C компилятор плюсов, и далее используя его), но понятно, что про это в таких дискуссиях можно не вспоминать.
И именно поэтому я
воспринял его как код для production'а.
Если оно уходит без предварительной проверки достаточности средств, то это — вопиющая некомпетентность брокера, потому что шортятся акции под залог, и следует проверить его размер, иначе у брокера будут проблемы, если с трейдера нечего взять, а цена резко улетит вверх.
Или, наоборот, предварительная проверка — это лишние сотни наносекунд, и вы проиграли тем, кто их не делает (а выражает все требования в типах и проверяет максимальное их число при компиляции, например).
Мы можем усложнять этот пример бесконечно, но смысл, опять же, я надеюсь, понятен: полагаться на внешние ограничения — не всегда возможно, либо не всегда остановит вас достаточно рано.
А именно так ведь на практике всё и происходит. Ваш коллега написал какой-то код, вы через полгода его меняете. Вы не то что «забыли здесь то», вы этого и не помнили никогда. Или вы ожидаете, что каждый контрибьютор в кодовую базу будет следить за всеми изменениями и поддерживать ментальный контекст каждого другого контрибьютора? Или даже если вы сами написали — через полгода вы уже забудете такие нелокальные финты.
Тогда для такой практики в коде нужны комментарии, составленные исходя из обозначенной проблемы.
Или времени и на это нет, и поэтому лучше отказываться от перегрузки функций?
ключ не в планировании на года вперёд, а в написании кода так, чтобы при его изменении компилятор вас тыкнул носом в максимальное количество мест, где ломаются предположения из-за ваших свежих изменений.
Это тогда потребуется писать каким-нибудь особо специальным образом, и не всегда удастся придумать решение для этого.
А обсуждаемый подход её только усиливает.
Если не думать и не хотеть решить проблему, сохранив решение, то так оно и останется.
Вот мне же почему-то удаётся находить решения, несмотря на то, что я, года два, если не три, как подзабросил C++, и только сейчас постепенно возвращаюсь к нему.
Удаётся, потому что хочу решить проблему и пытаюсь думать в нужном направлении.
Какой бы вы предложили?
Конкретный не предложу, но — очевидно, что в языке не должно быть UB. Уже только одно это — огромный шаг вперёд.
Или, наоборот, предварительная проверка — это лишние сотни наносекунд, и вы проиграли тем, кто их не делает (а выражает все требования в типах и проверяет максимальное их число при компиляции, например).
В обсуждаемом случае речь была явно не про HFT.
Мы можем усложнять этот пример бесконечно, но смысл, опять же, я надеюсь, понятен: полагаться на внешние ограничения — не всегда возможно, либо не всегда остановит вас достаточно рано.
Я говорил о том, что авторы статьи не потрудились выдумку свою соотнести с реальностью.
Нельзя отправить ордер с такого объёма с тем, чтобы он успешно исполнился, поэтому и обанкротиться таким образом нельзя.
У меня есть знакомый (в выдуманном мирке, разумеется, и навеяно это группой Nirvana), который очень просто обустроил свой крипто-фонд алгоритмической крипто-фиат-торговли. Сначала заделал лендинг, с кучей обещаний и его разрекламировал. Потом декларировал, что у него есть связи с кучей крутых математиков и программистов, и часть этих деклараций была вполне правдой. Потом начал собирать средства анонимных (это ерудна) и не только (а вот это дело) инвесторов, попутно нанимая знакомых к себе в штат. Условия приятные, за пару копеек можно числиться, а за половину-не-копеек-в-год, можно приехать, сидеть в опенспейсе и работать дальше над матбазой проекта, и все это как раз HFT называлось.
Но есть один нюанс: по неформальному договору, оклады платятся только в случае, если дельта (инвест-расход) растет, а как только иначе, то проект переходит к фазе graceful termination: оставшаяся бОльшая часть средств инвесторам отдается и последние зарплаты не выплачиваются (и тут тот самый нюанс). Товарищ реально смог провернуть это так, что никто не остался недоволен, "а вот на эти копейки он потом и жил".
И главное, что юридически все четко до каждого движения.
Тогда для такой практики в коде нужны комментарии, составленные исходя из обозначенной проблемы.
А как называются комментарии, которые проверяются компилятором, я всё время забываю? А, во, вспомнил: это типы!
Это тогда потребуется писать каким-нибудь особо специальным образом, и не всегда удастся придумать решение для этого.
Да не, просто заводите (как в данном случае) отдельный тип Quantity
, отдельный тип Price
, и так далее. В каком-нибудь идрисе можете там рядом таскать (стираемое в рантайме) доказательство, что quantity
неотрицательное, а в плюсах ограничиться рантайм-проверками где-то в конструкторе в дебаг-билдах, скажем (и гонять это всё в хвост и в гриву на pcap-дампах или на shadow-клоне потока событий с биржи с прода перед выкаткой новой версии на прод).
И я на всякий случай напомню, что мы в ветке под обсуждением решения с catch-all-шаблоном, который удалён. В моей классификации это вполне подходит под «особо специальный образ».
Если не думать и не хотеть решить проблему, сохранив решение, то так оно и останется.
Вы так говорите «думать», как будто у вас неограниченный бюджет на думалку.
Те ресурсы своего мозга, которые я сэкономил на борьбе с минными полями в языке, я могу потратить на алгоритмику, или на обдумывание предметной области, или на микрооптимизации, в конце концов.
Вот мне же почему-то удаётся находить решения
Мне всё же представляется несколько нечестным говорить одновременно «находить решения» и «это решение не для прода, а просто иллюстрация возможности».
Конкретный не предложу, но — очевидно, что в языке не должно быть UB.
Здесь согласен. Но как конкретно этот язык будет связываться с вашим кодом, который непосредственно отправляет там что-то на биржу, поддерживает книгу, и так далее (и который на плюсах или на расте или чём-то таком), чтобы оверхед был минимален?
Вот моя практика показывает, что собственный DSL проще всего. Но это на самом деле я просто люблю писать языки, поэтому у меня тут некоторый биас.
В обсуждаемом случае речь была явно не про HFT.
Сорян, я слишком привык к HFT.
Я говорил о том, что авторы статьи не потрудились выдумку свою соотнести с реальностью.
Перед авторами статьи стоит задача привести пример, который был бы понятен человеку вне предметной области без излишнего занудства, но против которого у среднего читателя в теме не возникнет большого желания придраться. Лично у меня этого желания не возникает.
Вы, конечно не обязаны мне отвечать, но если расскажете, мне было бы очень интересно. Так что это не полемика, но я, увы, имею чистой воды негодования:
Или, наоборот, предварительная проверка — это лишние сотни наносекунд, и вы проиграли тем, кто их не делает (а выражает все требования в типах и проверяет максимальное их число при компиляции, например).
И что, сотня наносекунд это 30 метров пространства для скорости света. Для принятия торгового решения это пренебрежимая величина, даже (не даже, а особенно) если это бинарный инсайд, ведь расстояния от источника до получателя на порядки больше, а если у вас есть конкурент, то он мог получить инсайд на часы раньше (по личным причинам), и вы эту информацию не имеете. Если это алгоритм, то неладен он будет, если не получает обратную связь с биржей, в рамках схожих таймфреймов на каждую сделку. Что можно просчитать за единицы микросекунд? Пересечение каких-нибудь EMA и RSI и тому подобное. И вот такое может быть поводом толкать без обратной связи на биржу паки сделок? Арбитраж, даже если биржи сферичны в вакууме определяется скоростью света, где лишние 30-300-3000-30000 метров просто рандомный фактор для топологии сети, а между биржами есть расстояние, порой на порядки эти числа превышающие.
Уж помилуйте, хочу живой пример (хотя бы детально выдуманный), где хотя бы (люблю повышать ставки) десятки миллисекунд задержки алгоритма при прочих равных, относительно другого игрока (пусть даже одного), позволит ему гарантированно выигрывать в частном случае (но он не знает, какой алгоритм вы используете). С этого момента поверю в HFT.
Если что, не к Вам претензия. Просто есть три субкультуры трейдеров, одни жалуются, что из-за HFT у них нет шансов, вторые убеждают, что каждая наносекунда важна, а третьи (я наверное там), понимают, что ну, если уж в секунду не уложился по обработке события, то, статистически вернее его было бы пропустить.
И что, сотня наносекунд это 30 метров пространства для скорости света. Для принятия торгового решения это пренебрежимая величина, даже (не даже, а особенно) если это бинарный инсайд, ведь расстояния от источника до получателя на порядки больше
Там, где эти наносеки были важны, сервера участников биржи стояли в одном физическом помещении с сервером самой биржи и соединялись с ним кабелями фиксированной длины (те, что поближе, были свёрнуты в такие смешные спиральки, похожие на как раньше у проводных телефонов шли к трубке).
Надеюсь, это отвечает в том числе на ряд ваших вопросов далее.
а если у вас есть конкурент, то он мог получить инсайд на часы раньше (по личным причинам), и вы эту информацию не имеете
SEC за такое по головке не погладит (если вы не конгрессмен, конечно — они S&P500 аутперформят весьма стабильно, и просто повторять сделки Нэнси Пелоси — хорошая идея, жаль только, что закон не требует от них публиковать инфу по сделкам сразу).
Если это алгоритм, то неладен он будет, если не получает обратную связь с биржей, в рамках схожих таймфреймов на каждую сделку. Что можно просчитать за единицы микросекунд?
Достаточно сложные модели, на самом деле.
Ну и ещё можно медленно обновлять коэффициенты линеаризованной модели в FPGA, нелинеаризованная версия которой обсчитывается и обновляется на большом x86_64. Но я не FPGA'шник и не видел их никогда, так что не знаю подробностей. Моё дело в трейдинге — обмазывать всё темплейтами, и чем больше компилятор жрёт памяти, тем лучше мои KPI.
Арбитраж, даже если биржи сферичны в вакууме определяется скоростью света, где лишние 30-300-3000-30000 метров просто рандомный фактор для топологии сети
Микроволновые соединения «по воздуху» совсем не сферичны и вполне применяются.
Но да, в тех случаях счёт не на десятки-сотни наносекунд, а на десятки и сотни микросекунд. 30 км — это уже аргумент, на самом деле, и для арбитража между чикаго и найзи, например, это уже роляет.
одни жалуются, что из-за HFT у них нет шансов
Фигня. В mid-freq и ниже ваш основной сигнал — это не кто ещё какие ордера на бирже сделал и не какое при этом было состояние книги вот сейчас, а какие новости вышли, какие новые законы, и всё такое (HFT'шники на них тоже смотрят, конечно, но сильно по-другому). HFT и не-HFT вообще в разных нишах.
Но, опять же, я не трейдер и не квант, я только темплейтами всё обмазываю и на сдачу DSL'ки делаю, а непосредственно предметная область для меня лично уступает в эффективности в роли снотворного только уравнениям в частных производных, поэтому на базе моих слов открывать трейдинг-стартап не стоит.
template <template <typename...> typename F, typename... FST>
auto
bind_first(FST &&... fst) {
return [&]<typename... LST>(LST &&... lst) {
F<FST..., LST...>{}(std::forward<FST>(fst)..., std::forward<LST>(lst)...);
};
}
На самом деле, просто замечательный пример. "У нас есть целая строчка кода на C++, а значит где-то есть неопределённое поведение" (с) — не помню кто.
Как любит говорить @IUIUIUIUIUIUIUI: "Задача со звёздочкой: найдите, что здесь не так" — пусть в данном случае эта задача и без звёздочки, если глаз намётан на такого рода проблемы (особенно если помнить, что пост про раст).
Намек на то, что в bind_first аргументы fst внутри лямбды захватываются по ссылке?
Просто тупые сишники не знают, что нельзя возвращать ссылку на локальную переменную. :сарказм:
Обмазанное лямбдой, шаблонами и вараргом, "чтоб никто не догадался"
Из той же оперы развод, что и приведение минус 2 к ансайнед.
В принципе, уровень статьи сохранен =)
Просто тупые сишники не знают, что нельзя возвращать ссылку на локальную переменную
А где здесь возврат ссылки на локальную переменную?
В корутине локальная переменная (аргумент функции bind_first) захватывается по ссылке и потом возвращается в retun std::forward<FST>(fst)
В корутине локальная переменная (аргумент функции bind_first) захватывается по ссылке
Где вот здесь:
auto const sendOrderGoog{bind_first<SendOrder>("Goog")};
есть локальная переменная? Под оной понимается указатель на строковый литерал?
Локальная переменная, это fst в шаблоне bind_first(FST &&... fst)
, которая захватывается в лямбду (в предыдущем комментарии я опечатался про корутину).
И да, в вашем примере (если бы он был рабочим), это была бы строковая переменная.
Локальная переменная, это fst в шаблоне
bind_first(FST &&... fst)
, которая захватывается в лямбду (в предыдущем комментарии я опечатался про корутину).
А какого типа эта переменная?
И да, в вашем примере (если бы он был рабочим), это была бы строковая переменная.
Что значит, строковая переменная?
Какой у неё тип?
Я — к тому, что в данном конкретном случае:
auto const sendOrderGoog{bind_first<SendOrder>("Goog")};
dangling reference не образуется, и последующее использование sendOrderGoog
в примере безопасно.
Но в общем случае, проблема, естественно, имеется.
И да, в вашем примере (если бы он был рабочим), это была бы строковая переменная.
Во-первых, пример не мой.
Во-вторых, если мы говорим о C++, то в C++ вообще нет такого понятия, как "строковая" переменная.
В-третьих, в месте вызова bind_first, а именно:
auto const sendOrderGoog{bind_first<SendOrder>("Goog")};
нет строки, есть строковый литерал типа const char[5]
, время жизни которого равно времени жизни всей программы. Внутрь bind_first он приходит, полагаю, как что-то вроде const char (&)[5]
(лень проверять как это реально должно записываться). Т.е. создаваемая в bind_first лямбда захватывает ссылку, которая не может протухнуть.
Так что прошу еще раз показать где в приведенном коде есть возврат ссылки на локальную переменную. Ведь вы ее видите, ну так покажите.
Что касается списков захватов у лямбды, т.е. разницы между [=]
и [&]
, то тут можно сказать следующее:
очень плохо, когда пишут
[&]
бездумно. Это прямой путь к проблемам;время от времени приходится писать
[&]
ради эффективности (чтобы избежать копирования тяжелых объектов), либо когда мы имеем дело с некопируемыми в принципе объектами.
Так что на первый взгляд обсуждаемая bind_first
выглядит потенциально опасной, но зато она позволит избежать проблем, например, в таком случае:
std::vector<std::uint8_t> raw_data(100uz * 1024uz * 1024uz, 0);
std::mutex data_lock;
std::condition_variable data_ready_cv;
auto worker = bind_first<some_worker>(raw_data, data_lock, data_ready_cv);
Поскольку сейчас можно и молодежно поливать C++ известной субстанцией, то самое время для "фу, небезопасно, можно отстрелить себе ногу".
но зато она позволит избежать проблем, например, в таком случае
std::ref/cref.
До кучи, этот пример не соответствует тз. std'шная-то функция копирует.
std::ref/cref.
Чуть менее чем бесполезны для защиты от протухших ссылок. Но дело даже не в этом.
Возьмем сильно сокращенный пример:
#include <iostream>
#include <string>
using namespace std::string_literals;
template<typename Str, typename Arg>
struct Worker {
void operator()(const Str & s, Arg i) {
std::cout << s << " " << i << std::endl;
}
};
template <template <typename...> typename F, typename... FST>
auto
bind_first(FST &&... fst) {
return [&]<typename... LST>(LST &&... lst) {
F<FST..., LST...>{}(std::forward<FST>(fst)..., std::forward<LST>(lst)...);
};
}
int main()
{
const auto s = "Some rather long string to avoid small object optimization, "s;
auto worker = [&s]() {
return bind_first<Worker>(s);
}();
worker(0);
}
https://godbolt.org/z/bbP8fh5hP
Если перенести s внутрь лямбды, порождающей worker, т.е.:
int main()
{
auto worker = []() {
const auto s = "Some rather long string to avoid small object optimization, "s;
return bind_first<Worker>(s);
}();
worker(0);
}
то мы ожидаемо получим проблемы, о которых вы говорите.
Но вопрос в другом: как вы не меняя Worker-а вставите std::cref в вызов bind_first, чтобы ничего не поломалось?
Вот так не получится:
bind_first<Worker>(std::cref(s));
Так ref/cref это не про владение, это к вашему последнему примеру про копирование большого вектора, и мьютекса с кондишн варом, которые не копируемые.
Нужно передать владение — мувайте, нужно расшарить — шарьте теми же shared_ptr'ами, не нужна "ссылочность" — обеспечьте копируемость. std'шная функция от этого отталкивается, в то время как приведённый изначально пример игнорирует этот аспект.
Так ref/cref это не про владение
Тогда к чему это?
это к вашему последнему примеру про копирование большого вектора и мьютекса и кондишн вара, которые не копируемые.
Как здесь помогут std::ref/cref?
Нужно передать владение — мувайте, нужно расшарить — шарьте теми же shared_ptr'ами.
Т.е. вы предлагаете всегда использовать shared_ptr даже когда немувабельный объект создать на стеке (или внутри другого объекта) и гарантировать его доступность другими средствами?
Тогда к чему это?
Предлагаю читать предложения до конца: "..., это к вашему последнему примеру про копирование большого вектора, и мьютекса с кондишн варом, которые не копируемые."
Как здесь помогут std::ref/cref?
Спасут вас от копирования огромного вектора и от ошибки компиляции при попытке скопировать объекты некопируемых классов. Ваш Кэп.
Т.е. вы предлагаете всегда использовать shared_ptr даже когда немувабельный объект создать на стеке (или внутри другого объекта) и гарантировать его доступность другими средствами?
Предлагаю использовать инструменты по требованию задачи. Не нужен shared_ptr — не используйте, пистолет у виска никто не держит.
Предлагаю читать
Предлагаю подумать о том, что если вопрос вам был задан, значит ваши слова не были поняты. А раз не были поняты, то их повторение ситуацию не прояснит.
Спасут вас от копирования огромного вектора и от ошибки компиляции при попытке скопировать объекты некопируемых классов. Ваш Кэп.
Во-первых, каким образом?
Во-вторых, как это все относится к обсуждению якобы опасной реализации bind_first?
Я вас просил изменить пример на использование std::cref. Сможете?
Предлагаю использовать инструменты по требованию задачи. Не нужен shared_ptr — не используйте, пистолет у виска никто не держит.
Напомню, что речь про реализацию bind_first. Текущая реализация позволяет вообще избежать копирования или использования каких-то дополнительных сущностей вроде std::shared_ptr.
Про shared_ptr заговорили вы. Значит вы (а не я) видите в этом смысл.
Это и есть проблема в плюсах. Вот мне кажется, что текущие компиляторы для х86/х64 кинут литерал в сегмент данных и ссылка никогда не пропадёт. Но это стандартом для всех систем и оптимизаторов! не гарантируется.
А теперь немного поменяем вызов
auto const sendOrderGoog{bind_first<SendOrder>(std::string("Goog")) };
Вот я уже сходу хз, куда это заведёт. Так что [&] - подковерное зло.
Но это стандартом для всех систем и оптимизаторов! не гарантируется.
За стандарт не буду говорить, не знаток.
Получить приключения со строковыми литералами можно и на x86/x64 при ручной загрузке/выгрузке DLL/so. Но C++ здесь обвинять можно только от обиды на весь свет.
А теперь немного поменяем вызов
Так мой поинт в том, что глядя просто на реализацию шаблона bind_first нельзя однозначно говорить есть в ней проблема с повисшими ссылками или нет. Тут тот самый случай, когда смотреть нужно не только на реализацию, но и на места использования.
Простой аналог из стандартной библиотеки C++ -- это std::min. Запросто может приводить к повисшим ссылкам.
Ну вот по подобным причинам я лет 20 назад решил, что С++ для меня слишком сложный о)
D выглядит гораздо получше в этой части, ну и в принципе, можно писать попроще и на С++.
К сожалению, выбранный тогда мной С# свернул куда то не туда (
Но в любом случае, возвращаясь к топику, Rust мне не продашь.
Но в любом случае, возвращаясь к топику, Rust мне не продашь.
Rust учитывает ошибки C++ и это пока что единственная реальная альтернатива C++ в тех нишах, где C++ пока еще нужен (нативный код, отсутствие сборщика мусора, контроль за происходящим, дешевая интеграция с ОС или даже работа без ОС).
За пределами этого (имхо) сужающегося сегмента нет смысла выбирать Rust или C++.
Минус не мой, но я тоже не согласен с безальтернативнтстью.
В gcc относительно недавно (что то раньше, что то позже) добавлены Ada, D, Modula-2.
А если допустим GC, хотя бы с приостановками, то список можно сильно расширить.
В gcc относительно недавно (что то раньше, что то позже) добавлены Ada, D, Modula-2.
D не взлетел, плюс он с GC, а без GC D нужен еще меньше, чем D с GC. Как бы это не было неприятно любителям D, но у D все еще хуже, чем у Haskell-я.
Modula-2 давно не актуальна за исключением чего-то совсем экзотического с точки зрения массового производства. Т.е. разработка бортового ПО для ракет на Modula-2 -- вполне возможно. Делать сейчас на Modula-2 какую-то СУБД или альтернативу MS Word... Вы серьезно?
Ada. Получше, чем у Modula-2. Но если брать всю экосистему с доступными библиотеками, книгами, статьями и пр., количества программистов знающих или хотя бы интересующихся, то вряд ли она может конкурировать с Rust и C++. Так что как язык для разработки софта -- вполне себе ОК, но как инструмент для написания тех же СУБД или редактора документов -- вряд ли.
А если допустим GC
То это будет совсем другая история и совсем другой выбор.
Весь вопрос в выбранной нише и задаче.
нативный код, отсутствие сборщика мусора, контроль за происходящим, дешевая интеграция с ОС или даже работа без ОС).
При такой постановке все три языка подходят, а вот дальше требования надо смотреть.
Субд или редактор на Расте я тоже себе плохо представляю.
D не взлетел в массовости, и я его не использую по ряду объективным причин, но при необходимости любой программист на С-подобных языках пересядет на него за пару дней. Другие ЯП таким похвастаться не могут.
Субд или редактор на Расте я тоже себе плохо представляю.
Зря. Rust более чем годный язык для таких задач.
Я вижу его применение для гораздо менее масштабных задач. Проблема [пока] в экосистеме, в людях, Да и самый медленный в мире компилятор...
Время рассудит, хотя его прошло уже достаточно, 10 лет, и пока не наблюдал ни субд ни редакторов.
пока не наблюдал ни субд ни редакторов.
У меня знакомый работает в Picodata. Не знаю, на чем у них сама Picodata сделана, а вот Radix (замена Redis-а) у них пишется на Rust.
Ну и как по мне, если условный PostgreSQL умудряются тянуть на чистом Си, то уж на Rust-е его аналог было бы делать и сподручнее, и безопаснее.
Вот мне кажется, что текущие компиляторы для х86/х64 кинут литерал в сегмент данных и ссылка никогда не пропадёт.
В данном случае вам кажется совершенно правильно.
Но это стандартом для всех систем и оптимизаторов! не гарантируется.
Почему вы не проверили это своё утверждение и не предупредили, что, вполне возможно, это вам только кажется?
Тем более, что сейчас достаточно задать ИИ всего два вопроса:
Каково время жизни строкового литерала в программе на языке C++?
и
В каком пункте стандарта C++17 говорится об этом?
чтобы получить исчерпывающий ответ (я здесь использовал Gemini, он у меня — "дежурный" ИИ) и просто открыть указанные ИИ 2 пункта стандарта с cppreference.
Первый (по ссылке нужный фрагмент подсвечен светло-зелёным):
16 Evaluating a string-literal results in a string literal object with static storage duration, initialized from the given characters as specified above. Whether all string literals are distinct (that is, are stored in nonoverlapping objects) and whether successive evaluations of a string-literal yield the same or a different object is unspecified. [ Note: The effect of attempting to modify a string literal is undefined. — end note ]
и второй:
1 All variables which do not have dynamic storage duration, do not have thread storage duration, and are not local have static storage duration. The storage for these entities shall last for the duration of the program ([basic.start.static], [basic.start.term]).
Как видите, гарантируется.
Вот мне кажется, что текущие компиляторы для х86/х64 кинут литерал в сегмент данных и ссылка никогда не пропадёт. Но это стандартом для всех систем и оптимизаторов! не гарантируется.
Вообще-то гарантируется: строковые литералы гарантированно имеют static storage duration:
Evaluating a string literal results in a string literal object with static storage duration.
Просто тупые сишники не знают, что нельзя возвращать ссылку на локальную переменную. :сарказм:
А если "локальная переменная" сама является ссылкой?
Там, на самом деле, не локальная переменная, а параметр функции, который является ссылкой.
Будет ли ссылка на него ссылкой на локальную переменную, находящуюся в этой функции?
Кстати, сомневаюсь, что тупой в состоянии разобраться в C++.
"Задача со звёздочкой: найдите, что здесь не так"
Не удалось подумать, @eao197 уже подсказал.
Но в данном случае при показанном конкретном примере использования dangling reference не образуется.
Я, на самом деле, когда писал, сначала задумался, что туда вписать, решил сначала временно вписать туда &
, сделать основное, а потом вернуться к вопросу.
Основное сделал, а вернуться к вопросу забыл.
template <template <typename...> typename F, typename... FST>
auto
bind_first(FST &&... fst) {
return [=]<typename... LST>(LST &&... lst) {
F<
std::add_lvalue_reference_t<
std::add_const_t<
std::decay_t<FST>
>
>...,
LST...
>{}(fst..., std::forward<LST>(lst)...);
};
}
Как-то так, в первом приближении, должно выглядеть.
У вас тут будет копирование фст внутрь лямбды, а не мув, который вы вероятно ожидаете видя &&. Это потому что в цпп валью семантика недоделанная, я бы лично хотел мув везде по дефолту, но чтобы получить мув надо написать нечитаемую портянку вида [fst = std::forward<FST>(fst)...] Я понимаю что сделали констстентно со всем остальным, но легче и правильнее консистенси не приносит. Я вообще не верю/отрицаю значимость консистенси в целом, а не только здесь, но это офтоп.
У вас тут будет копирование фст внутрь лямбды,
Верно, следует захватить, применив std::forward
:
template <template <typename...> typename F, typename... FST>
auto
bind_first(FST &&... fst) {
return [... fst{std::forward<FST>(fst)}]<typename... LST>(LST &&... lst) {
F<
std::add_lvalue_reference_t<
std::add_const_t<
std::decay_t<FST>
>
>...,
LST...
>{}(fst..., std::forward<LST>(lst)...);
};
}
а не мув, который вы вероятно ожидаете видя &&.
Нет, в суть ссылок я вник как следует уже давно.
Здесь просто не продумал этот момент.
Стоит добавить, что размер приведённых в статье типов-обёрток такой же, как и у примитивных типов. Нулевая стоимость абстракций и стирание типов во время компиляции:
struct Quantity(pub u64);
assert_eq!(std::mem::size_of::<Quantity>(), std::mem::size_of::<u64>());

Тут справа есть панелька :)
для плюсов давно известно крайне эффективное решение, которое позволяет различать даже два типа, использующих одинаковые базовый тип а-ля float за счет введения системы тэгов
это позволяет на этапе компиляции, к примеру, проверять корректности типа с размерностями, типа киллограммы, метры в секунду и т.д.
https://www.reddit.com/r/cpp/comments/rrb3lg/how_to_make_stronger_types_in_c_easily/
Как Мэтт Годболт «продал» мне Rust (рассказав о C++)