Как стать автором
Обновить

Комментарии 108

Нестрогая типизация c++ была не очень хорошей идеей

C++ - это в целом полигон для отработки идей, многие из которых оказались плохими.
Хотя, нестрогая типизация досталось ему от C.

А какие из новых идей, опробованных в C++, оказались хорошими?

Ну вроде как цпп отец раи, который имхо весьма полезная идея. Я вот когда был маленький и мне надоело в дельфи писать очередной типовой контейнер, но для другого типа, а мне кто то сказал что в цпп есть шаблоны - вот тут то все и завертелось. Цпп первым из мэйнстрим языков освоил обобщенный код. А вот ещё фундаментальная идея которая правда вот совсем не доделанная в цпп - валью семантика. Для меня этих 3х идей было 20+ лет назад по сравнению с явой, дельфи, пхп и шарпами было достаточно, сейчас уже не достаточно. Я не историк языков программирования, не знаю насколько свежими были эти идей на тот момент.

Ну вроде как цпп отец раи

CLU.

в цпп есть шаблоны

ML и параметрический полиморфизм. В C++ обкатывали разве что идею «как бы параметрический полиморфизм сделать максимально криво, но чтобы потом было весело делать головоломки и отсеивать людей на интервью». Хорошо разве что для job security.

валью семантика

совсем не доделанная в цпп

Здесь согласен.

В C++ обкатывали разве что идею «как бы параметрический полиморфизм сделать максимально криво, но чтобы потом было весело делать головоломки и отсеивать людей на интервью»

Интересно, а где в 1990-ом году это было сделано "максимально прямо" и почему эти мощные конкуренты (если они вообще были) до массового применения так и не дошли?

Потому что слишком не похожи на алгол были

Интересно, а где в 1990-ом году это было сделано "максимально прямо"

Написано в фразе прямо перед процитированной вами, но я повторюсь: в ML (ранние 70-ые).

Более того, даже тот же хаскель (с ещё более полиморфным полиморфизмом) появился в том же 90-м.

и почему эти мощные конкуренты (если они вообще были) до массового применения так и не дошли?

Полигон — это когда массовое применение прямо там же, да.

Не двигайте гоалпосты.

Полигон — это когда массовое применение прямо там же, да.

Массовое применение Haskell-я и ML?

Простите, но мне не интересны новости из параллельных вселенных. Как и ответы на выдуманные вами самими вопросы.

Не двигайте гоалпосты.

В вашем уютном манямирке, в котором Haskell уже массово применяется, возможно, и не знают про то, что не следует указывать людям что им делать, чтобы людям не пришлось указывать вам направление движения.

Массовое применение Haskell-я и ML?

Про необходимость массовости полигона была ирония. Извините, если получилось слишком неявно.

Как и ответы на выдуманные вами самими вопросы.

Вопрос о массовости вами задан не был? Ну ок (тоже ирония, если что).

Я так понимаю, что сейчас общаюсь с молодым человеком, который потратил всю юность и молодость на изучение C++, а потом горько пожалел об этом и опубликовал на Хабре статью про свое разочарование под другим ником.

Прикольная фамилия у этого Мэтта Годболта: дословный перевод "Божественный болт" или "Болт господень"? Знатоки английского - как правильно?

это одно слово , а не два. Дословно переводится

это два слова - стрела господня дословно

bolt - арбалетная стрела

Bolt - это снаряд в более общем понимании. Например, thunderbolt – однозначное название молнии, arcane bolt – чародейский выстрел из WoW и т.п.

Молния Бога

Кто о чем, а вшивый...

"добавим защиту от отрицательных значений, перейдя с i64 на u64. Неужели всё так просто" - не думаю. Если u64 будет результатом некорректного преобразования, то это не будет очевидно в случае с u64. просто будет какое-то положительное число. а вот если в i64, то отрицательное уже сигнализирует об ошибке, значит не стоит продолжать.

А как вы в 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ых, ее просто нет в едином виде.

  1. Добавление библиотеки почти всегда квест. Многие библиотеки их быдлокодерами разработчиками предлагается собирать и ставить по уникальной инструкции прямо в глобальное окружение (!) для того чтобы использовать в проекте.

  2. Есть сборщик CMake, который состоит на 100% из костылей и по хорошему никогда не должен был существовать. Минимальный проект заводится с простыней кода конфигурации на отдельном недоязыке (обычно ее копируют из старого проекта, поскольку почти никто не понимает как оно работает).

  3. Есть менеджеры зависимостей Conan и vcpkg, которые чуточку исправляют ситуацию, но с отдельными registry, плохой совместимостью между собой, точно также обложены лютыми костылями и точно также требуют конфигурации на ровном месте.

  4. Наконец есть пожалуй единственный пригодный на текущий момент менеджер сборки и зависимостей, xmake, но у него небольшая экосистема, и проблемы низкоуровневых зависимостей зачастую самым неожиданным образом протекают наверх, сборка падает, и снова начинается ад с костылями и ручным разруливанием различной конфигурации.

Именно из-за всего этого ада в мире C++ стали настолько популярны header-only библиотеки, состоящие из одного заголовочного файла. Вовсе не из-за особых хакерских умений или минимализма разработчиков, а просто для того, чтобы их поделия вообще можно было хоть как-то подключить в другой проект.

А что же в Rust? А в Rust, без преувеличения, лучший менеджер зависимостей среди всех известных мне языков — Cargo, и одна общая экосистема на всех, что дает языку огромный буст к развитию. Чтобы сборка завелась нужно написать... ноль конфигурации! Из коробки действуют разумные соглашения по умолчанию, код лежит в src, точка входа в main.rs. Хотите подключить библиотеку? Одна строчка в Cargo.toml. Хотите подключить из своего registry или из git'а? Уложитесь в ту же строчку. Именно эта простота, а не семантика перемещения или иные языковые особенности, на мой взгляд позволила Rust'у так быстро обойти C++.

Да, Rust немного посложнее синтаксически, это фанатам C++ тяжело принять, но разобраться того стоит. Все лучшие практики из C++ core guidelines в Rust внедрены и проверяются на уровне языка.

А ещё появился свеженький rustrover и теперь тулинг просто отличный!

JetBrains Gateway: и проект нативно переезжает на другой хост. Не просто выполнение, а вся кодовая база на хосте. Например, имея винду или мак, можно билдить на убунту.

CMake не только для С++, честно говоря.

Но как всегда (в жизни, по закону бутерброда) - самый отвратительный билдтул стал "стандартом".

Угу. Видел даже поддержку .NET в нём. Но кто в здравом уме будет это использовать для .NET?

Я использовал, было весело )

Да не, самый отвратительный это все же autotools.

Да, Rust немного посложнее синтаксически, это фанатам C++ тяжело принять, но разобраться того стоит.

Не то, что бы Rust от этого становился сложнее C++. В С++ вся эта сложность заботливо перенесена в стандартную библиотеку и компиляторы. :)

Но порог входа выше, да.

Порог входа в компилируемые программы, состоящие более из чем пары строк, выше. Порог входа в программы, которые после компиляции работают как задумано, на пару порядков ниже.

Да, Rust немного посложнее синтаксически, это фанатам C++ тяжело принять, но разобраться того стоит. Все лучшие практики из C++ core guidelines в Rust внедрены и проверяются на уровне языка.

Вообще то Раст гораздо проще плюсов, просто неудобнее.

Уберите экслюзивное владение обычными переменными (не ресурсами) , сохранив преимущества, и был бы язык года (ну почти)

«обычные» переменные (вроде примитивных типов) помечены Copy, и просто всегда автоматически копируются, избавляя от проблем с владением. А когда надо управлять куском памяти побольше чем u128.... ну так, память это вполне себе ресурс :р

только Cmake научился так же с гита напрямую зависимости подтягивать и аккуратненько в папке проекта складывать, синтаксис приличный если писать на новых версиях и изначально правильно, а так, любая либа легко заводится с 2 команд указанных в гите, не говоря уже про новые системы сборки, которые и синтаксически просты и с зависимостями там дела лучше обстоят

с инета я могу подтягивать гитом, локально на примере просто:
include_directories(lib/asio-1.30.2/include)

Через полгода ждем статью "как я перешел с руст на ххх т.к. руст не годится для чтего-то сложнее хеллоу ворда"

И 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 это решено концептуально и на корню.

Ну вообще зависимости обычно собирают отдельно с их родными ключами с использованием одного из пакетных менеджеров (я лично предпочитаю vcpkg), а собственный код - отдельно.

Так а что делать с заголовочными файлами из этих библиотек, если в них есть вот такие огрехи?

Есть механизмы контекстозависимого отключения предупреждений, например.

Этот код появился потому что компилятор не ругался. Вопрос в том, почему он вообще позволял небезопасное неявное приведение типа.

А тут не на что ругаться, абсолютно ничего "небезопасного" в приведенном мной коде нет. Прописывание явного приведения никакой "безопасности" тут не добавляет.

Действительно, что может быть опасного в присвоении знакового значения в беззнаковое поле или наоборот..

Безопасности тут добавляет проверка if(y < 0), а не явное приведение y к unsigned. Если есть проверка, то явное приведение не добавляет никакой "безопасности". Бездумное приведение же без проверки просто заткнет компилятор, а ошибка останется - как и в расте, кстати.

С точи зрения простого компилятора эта проверка ничего не добавляет. И даже если компилятор научить сужать типы (а это не про С), это не поможет с проверками вида if( x < y ).

С точи зрения простого компилятора эта проверка ничего не добавляет.

Это уже другой вопрос. Для надежного отлавливания ошибок конверсии между знаковыми и беззнаковыми типами одного требования явного приведения мало - прописывание этого приведения само по себе ничего не дает, ошибка остается. Если в приведенном мной куске кода завтра один джун пропишет foo((unsigned)y), чтобы заткнуть компилятор, а послезавтра другой джун выкинет if по ошибке, то компилятор ничего не заметит (как и в расте, кстати).

Если использовать правильную конверсию, а не as u32, то ошибка таки вылезет. Другое дело, что джун так и напишет.

Напишите пример правильного преобразования

u32::try_from(-1); // Err(TryFromIntError(()))

Спасибо огромное

С вызовом функции любой дурак язык сможет

Проверка не является одним целым с вызовом и при очередном рефакторинге может быть (ошибочно) стёрта.

Я бы не назвал такой код «абсолютно валидным и беспроблемным». Если программист хочет взять на себя ответственность — пусть делает это явно.

Проверка не является одним целым с вызовом и при очередном рефакторинге может быть (ошибочно) стёрта.

Конечно! Но вся штука в том, что точно так же она может быть ошибочно стерта и при наличии явного приведения 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", но при изменении контекста предупреждение будет расценено как "новое" и будет снова отображено. А явные касты тут только мешают.

А как эти роботы определяют, что входит в контекст? Эвристики? Или тупо по границам скоупа?

Точно не могу сказать :)

@nin-jin

Я собрал данный код на clang-19 и gcc-14, действительно ошибки безопасности здесь никакой нету, приведение вполне себе явно из-за проверки, компилятор не выдает ошибки. Стандартом для компиляторов специально эти варнинги отключены, так как они часто ложные срабатывания имеют и не проверяют пользовательские проверки переменных как `if (y <0)`

Не надо делать лишние явные приведения, компилятор сам сообщит когда действительно опасные действия Вы делаете с типами, особенно хорошо по-умолчанию работает у clang компилятора такое.

Любой адекватный программист тут ожидает ошибку компиляции и требование к программисту взять ответственность за последствия такого каста на себя:

int main()
{
  int y = -100;
  unsigned int x = y;
}

большинство программ обрабатывают данные, которое проходят через миллион мест, никакой язык, ни какие методы не помогут на этапе компиляции.

для всего этого придумали системы обработки ошибок, контракты и т.п.

не нужно решать высокоуровневые, прикладные задачи, средствами, конструкциями языка.

С другой стороны, существует немало задач которые могут быть решены и успешно решаются конструкциями языка и компилятором - не присваивай значение неправильного типа, не устраивай гонок данных между потоками, не используй память после освобождения и т.п. Эти средства позволяют не допустить подобный невалидный код в принципе и доказали свою эффективность. А отрицать их это просто луддизм

Нет, такие вещи не решаются методами раста. Вы можете запретить многопоточное программирование вообще и сказать, что это решение проблем гонок - но если вам всё таки нужно написать что-то многопоточное, вам не поможет здесь раст никак.
Максимум он избавит студента от глупой ошибки, именно что студента и от глупой. А обычному программисту он просто вставляет палки в колёса, не давая делать то что нужно

Вас не смущает, что почти все программисты на 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

https://doc.rust-lang.org/std/convert/trait.Into.html

Вот в плюсах это же целое дело, каждый раз смотреть, можно ли тип между тредами передавать или нет. Это только в документации есть. А если ее нету, а как понять.

Ошибка эта вот довольно распространенная и тяжело уловимая. Раст просто не даст ее сделать.

большинство программ обрабатывают данные, которое проходят через миллион мест, никакой язык, ни какие методы не помогут на этапе компиляции.

Зависимые типы помогут (и помогают).

Конкретно для представленных в статье примеров помогает конструкция newtype, которую, увы, приходится обвешивать стандартными deriving. А применение Руста такое же долбоебическое, как и С++.

А почему он продал тебе именно руст а не голанг? Видимо, тебе очень хотелось написать хвалебную статью про руст, но никак повода не находилось?)

И он аж целых двадцать лет писал на плюсах, но не знает, что использовать для денежных типов. На 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 сравнимого порядка в год.

Стоит добавить, что размер приведённых в статье типов-обёрток такой же, как и у примитивных типов. Нулевая стоимость абстракций и стирание типов во время компиляции:

struct Quantity(pub u64);
assert_eq!(std::mem::size_of::<Quantity>(), std::mem::size_of::<u64>());
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации