Pull to refresh

Comments 362

C++ он и есть от того С++, что к языку С добавили эти самые ++. А если хотите чистые ++, то тогда тут нужен другой язык.

Вот вам другое мнение для размышления: почему язык C++ развивается не согласованно со стандартами языка С? Как это подразумевалось при его создании чтобы расширить возможности С. А рождает вместо этого противоречивые костыли.

Этот язык программирования появился в начале 80-х годов ХХ столетия. Придумал его Бьерн Страуструп. Создавался С++ на базе существовавшего тогда С. Первоначально, Страуструп назвал новый язык «С с классами», так как в нем были реализованы классы. Помимо этого, в новый язык программирования были добавлены виртуальные функции, ссылки, константы и многое другое. Также здесь появился новый стиль комментирования (привычные многим //). В результате получившийся язык программирования перестал быть дополнением в С и стал самостоятельным.

Вполне себе развивается, можете будущий C23 стандарт глянуть

там практически ничего не добавляется, а компилируется С++ код с С99 или чем нибудь таким

компилируется С++ код с С99 или чем нибудь таким

Не до конца понял что с чем компилируется. Если вы имели ввиду компилить C++ код под стандарт C99, то далеко не всякий C++ код будет компилиться таким образом.

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

Никто и не ожидает от небольшого низкоуровневого языка кардинальных изменений.

До стандарта C++17 совместимым с C++ стандартом был C98. Не C99.

Начиная со стандарта C++17 совместимым с C++ стандартом стал C11.

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

Зачем чинить инструмент, который не сломан? Как исправлять инструмент, который достаточно эффективно покрывает задачи своего применения?

С++ который лишён недостатков уже придумали.

Это Java(+Graalvm).

И да, union был в С не для того что написано в вашем примере, а для работы с аппаратурой, битовыми полями. Особенно нестандартных аппаратных приблуд, в которых половина слова биты значений другая половина -- короткие поля значений. Для того чтобы расписать регистр PSW на чтение/запись. И всё это без геморроя с масками и сдвигами.

Hidden text

На С/С++ с 1989 года, на Java 11/Graalvm - c 2020, и назад на плюсы ни ногой.

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

В 2022 году не принято делать наброс без цифр, альтернатив и сравнений.

Чем вам помешал Object и чем плох ооп? Какие альтернативы?

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

Ну, Object плох тем, что с ним ООП неполноценное :-) "Наследование убивает инкапсуляцию", как учит нас GoF, криво цитируя чужую статью :-)

UFO just landed and posted this here

задач, на которые объекты ложатся хорошо, мало.

Задач, которые хорошо ложатся на ФП, в реальном мире ещё меньше. И, что намного хуже, людей, на мозг которых нормально ложится ФП, вот прям совсем мало. Можете сравнить число резюме на С++/С#/Java/PHP и на вашем хаскеле + F#. А лидируют, внезапно, javascript и python, где большинство кандидатов пишут в старом-добром императивном стиле "пойди туда, возьми то", без всяких извратов с ООП и ФП...

UFO just landed and posted this here

Объекты прекрасно компонуются. Они для того и были придуманы - декомпозиция кода на объекты, чтобы компоновать уже их.

Абстракции для того и нужны, чтобы скрывать несущественные детали, в частности эффекты.

UFO just landed and posted this here

Книжек про монады, моноиды, функторы, эндофункторы и прочие фп паттерны - не меньше.

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

Далеко не все эффекты являются существенными, не врите.

UFO just landed and posted this here

Что такое "уровень GoF"? Вы не поверите, но ООП паттерны - это тоже не более чем интерфейсы с правилами.

Спорить про удачность можно долго, но утверждать о некомпонуемости объектов - это либо дилетантизм, либо троллинг. Ни то, ни другое вас не красит.

Не получается, так как те два метода будут вызваны автоматически.

UFO just landed and posted this here
UFO just landed and posted this here
Для развития педагогических навыков — какие символы тут непонятны?

Мне понятно, но подозреваю, что средний сишник может оказаться не готов к такому способу указания типов.

UFO just landed and posted this here

Открою страшную тайну, но стрелочки много где встречаются. Даже в математике. Выражение типа M a → (a → M b) → M b вполне можно прочитать в логике предикатов, например, где они означают импликацию, и которая сишнику, по крайней мере теоретически, значительно ближе. Хотя он, конечно, прочитает какое-то странное "разыменование указателя".

UFO just landed and posted this here
UFO just landed and posted this here

Тут есть ещё другая проблема. Когда я вижу стрелочки и кванторы в математическом тексте - они читаются на вскидку. А когда в коде на незнакомом языке, это вызывает вопрос: "Чёрт возьми, что они в этом языке значат?!!"

Кроме того, символы, не вводимые с клавиатуры, - это триггер "Осторожно! Маргиналы!"

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

Это когда каждый пример разжёвывается на десяток страниц с кучей кода, мотивацией, и так далее.

Тут нужно бы делать скидку на уровень аудитории, для которой это все разжевывается.

Много слов нужно не потому, что ООП сложное или паттерны проектирования мудрены. А потому, что большая часть целевой аудитории таких книг, грубо говоря, виртуальный деструктор уяснить для себя не может.

UFO just landed and posted this here

отсылки к достаточно специфичным вещам, от оконных систем до всяких парсеров-компиляторов.

Что такого специфического, например, в оконных системах?

Виртуальный деструктор большинство из них при этом осилило.

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

UFO just landed and posted this here

Но у народа возникают сложности.

О том и речь. Средней руки программистам даже паттерны разжевывать нужно до состояния мелкой кашицы (хотя в 95% этих самых паттернов, имхо, вообще ничего нет особенного, они переоткрывались многократно многими даже не задумываясь).

Design pattern это библиотека, которую невозможно написать, потому что язык не позволяет (c) не_мой

Про компоновку объектов можете почитать в этой статье. Там же есть целый параграф с описанием реактивного фабричного метода и других паттернов, которых вы не найдёте в GoF.

UFO just landed and posted this here

Можно аналогичные правила про фабричный метод, обсервер и адаптер?

А зачем? :) Мне вот обычному смертному и без этих правил удобно ими пользоваться.

Вот монады, хоть тыщу раз имеют эти правила, я так и не понял зачем нужны. Я уж не говорю о том, что монадой по итогу оказывается куча разных сущностей и их монадическая природа ваще не очевидна на первый взгляд. В то время как фабричный метод я объясню ребенку за 10 минут.

Это когда каждый пример разжёвывается на десяток страниц с кучей кода, мотивацией, и так далее.

Сколько статей про монады было на Хабре, но мир их так и не понял.

UFO just landed and posted this here

Как понять, когда у меня адаптер, а когда — бридж?

А зачем?

Как понять, когда фабричный метод применим?

Как на счёт "когда захочется"? :) С тем же успехом я могу спросить "когда применим нож?". Ответ: во многих ситуациях, в основном когда надо что-то порезать, но вообще можно и гвоздь забить, если сильно прижмёт.

Для того, чтобы один раз написать кучу разных абстракций.

Описание правильной применимости которых точно также займёт по целой книге. Просто никто эти книги не пишет. Не потому, что они не нужны, а просто потому, что ФП как таковое никто не использует.

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

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

Но вам же не нужно знать особенности теории рекурсивных функций, чтобы вполне себе писать на обычных императивных языках?

Как и вам не нужно знать паттернов ООП, чтобы успешно писать на ООП языках) Я вот вообще про существование этих паттернов узнал через два года коммерческой разработки. Как оказалось, 80% из них я просто переизобрел, а дизайн некоторых языков просто подталкивал ими пользоваться.

UFO just landed and posted this here

Можно пример книжек про монады, моноиды и эндофункторы примерно того же уровня, что GoF?

Пусть и не книжка и пусть наверно не настолько глубоко, но вот
https://stepik.org/course/693/promo
целый курс по монадам

Ага, поэтому по тому, как их компоновать, целые книжки пишут, и потом обсуждают, что за SOLID такой, что за билдер, что за визитор
Да, и это всё из-за того, что сейчас заканчивается первая стадия ООП в духе С++ (слабо-инкапсулированные сильно-связанные объекты). Истинное ООП Следующий этап ООП, который должен был быть — в духе Smalltalk (сильно-инкапсулированные слабо-связанные объекты, а сам Smalltalk просто опередил свое время). Но этот этап пока отложился из-за хайпа ФП. Поэтому я предполагаю, что будет прыжок сразу на третий этап — синтез ООП+ФП, где объекты будут инкапсулировать в себе лишь состояния системы, а все действия будут выполняться в блоках в стиле ФП.
UFO just landed and posted this here
UFO just landed and posted this here
Такое поведение, которое вдруг запрещает действие с данными (по уважительной причине, заранее не ведомой инициатору изменения) и предписывает иные действия. Или же, в зависимости от входных данных, выполняются разные действия (вроде оператора switch case). И это все должно происходить внутри объекта независимо от того, кто его вызвал. Суть в том, что мы пишем поведение один раз и потом уже спокойно обращаемся к объекту, без необходимости проверять все кейсы на корректность — поэтому оно и должно быть внутри объекта…
UFO just landed and posted this here
Тогда я вернусь к вашему предыдущему сообщению
Зачем объекты для инкапсуляции состояния, когда состояние отлично инкапсулируется в виде глупых и не имеющих собственного поведения структур данных (у которых просто не экспортируются кишки), плюс функций, объявленных в том же модуле?
и задам вопрос: что по вашему значит «собственное поведение» в этом контексте?
UFO just landed and posted this here
Ясно, спасибо. Я тоже на этой стороне, просто не пойму почему «структуры данных (у которых просто не экспортируются кишки), плюс функции, объявленные в том же модуле» нельзя считать объектами (да, не в терминологии современного ООП, но все же)?
UFO just landed and posted this here
А зачем напрягаться, если можно просто рассуждать в терминах каких-то типов и функций на них?
Тонко…

состояние отлично инкапсулируется в виде глупых и не имеющих собственного поведения структур данных (у которых просто не экспортируются кишки), плюс функций, объявленных в том же модуле?

Так это и есть объекты. Данные + методы. Разве что без ключевого слова class.

UFO just landed and posted this here

Вот только есть один нюанс.

Есть вполне себе ООП-языки, в которых допустимы функции верхнего уровня (т.е. не являющиеся методом объекта). Наличие оных не влияет на "парадигменную ориентированность" (если это так назвать) языка.

Как-то ваш синтез очень на процедурное программирование смахивает.

Да, глядя изнутри какого-то объекта, мы программируем почти процедурно — и это хорошо.

несущественные детали, в частности эффекты

После этих слов в ФПшном поезде начался сущий кошмар.

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

На лидирующем javascript как раз таки очень любят писать в функциональном стиле.

Ага, эмулируя объекты через грязные хуки.

UFO just landed and posted this here

Потому что "функциональные" компоненты так называются только потому, что являются функциями с точки зрения языка, ФП тут ни причем.

Не понял о каких хуках речь вообще, разве что вы к разговору о ЯП приплели фронтенд фреймворк.

Вам не нравится union - ну так получите вместо него принцип "всё является объектом".

К тому же, я так и не понял, в чем проблема с ООП и виртуальными методами

в том что они не нужны, реализуются на С++ удобно без ключевого слова virtual / override

На гитхабе с реализацией из статьи также описано какие проблемы уходят

Напишите пожалуйста как удобно без вирутальных методов реализовать нечто подобное

struct GraphicObject
{
    virtual void draw() = 0;
};

std::vector<GraphicObject*> graphic_items;
....
for (auto item : graphic_items)
    item->draw();

Думаю будет выглядеть как-то так


template <typename T>
struct GraphicObject {
    static void do_invoke(const T& self) {
        self.draw();
    }
};

struct Circle {
    ...
    void draw() {...}
};

struct Rectangle {
    ...
    void draw() {...}
};

std::vector<aa::any_with<GraphicObject>> graphic_items;
graphic_items.push_back(Circle{});
graphic_items.push_back(Rectangle{});

for (auto& item : graphic_items) {
    aa::invoke<GraphicObject>(item);
}

Но насколько это удобно, от меня пока ускользает. Особенно если нужно иметь более одного метода в интерфейсе. Например, GraphicObject::move.

Интересно конечно, но на каждый метод создавать класс обёртку это вообще не удобно, также какие проблемы уходят при этом решении, по-моему их становится только больше, получается если код практически полностью полагается на вызовы виртуальных методов везде придётся писать aa::invoke<method class>? К тому же судя по реализации any_with он работает только с movable классами. И давайте ещё немного немного усложним задачу.

struct Widget : GraphicObject
{
    virtual void draw() override
    {
        ...
    }
};

struct Label : Widget
{
    virtual void draw() override
    {
        Widget::draw();
        ...
    }
};

draw можно будет поменять на event например, будет более логично.

в чём усложнение то задачи?

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

в чём усложнение то задачи?

Вызов метода предка.

По остальному, это всё и так есть, достаточно просто не использовать виртуальные методы, вы назовите какие проблемы уходят если убрать виртуальные методы. Потому что без виртуальных методов можно и сейчас писать, но если захочется полиморфизма придётся писать огромную кучу классов обёрток, при этом что делать если нужно лишь частично переписать класс предка, а часть реализаций оставить тех что были, копипастить их? А если требуется вызов метода предка + добавление своей реализации как в усложнённом примере что я кинул? То что вы предлагаете, будет лишь раздувать код в сотни раз, а больше кода больше ошибок, я уж молчу о всяких vasssist ах и решарперах они станут совсем бесполезными в данном случае, а ошибки компиляции будут ммм загляденье, время компиляции тоже наверно скажет спасибо.

move не нужно будет иметь потому что есть aa::move, то есть это ведёт себя как объект, а не как указатель на объект.

Так что будете писать a = b; а не a=b->copy() и прочая муть.

Методов может хоть 2 хоть 102, просто указываете. По неймингу понятно, что вы не совсем поняли суть, GraphicObject это не интерфейс целиком, а лишь один метод, из множества методов можно собрать интерфейс

Так что назовите его скажем Draw

и потом

using any_drawable = aa::any_with<Draw, aa::move, aa::copy>;(что вам там нужно то и берите)

Ну и в examples там показано, что можно в методы добавлять сразу интерфейс, чтобы писать не aa::invoke<Draw>(v), а v.draw();

move не нужно будет иметь потому что есть aa::move, то есть это ведёт себя как объект, а не как указатель на объект.

Да только там (судя по реализации) на каждый объект происходит по сути placment new и вызывается конструктор копирования\перемещения а если ещё и стандартного размера буфера не хватит то и выделение памяти для каждого any_with.

что вам там нужно то и берите

И на каждый писать класс обёртку? Отлично.

Ну и в examples там показано, что можно в методы добавлять сразу интерфейс, чтобы писать не aa::invoke<Draw>(v), а v.draw();

Да посмотрел, дикое извращение лишь бы не использовать vtable. Да и там по сути есть vtable только своя реализация.

Вам напомнить, что при использовании виртуальных функций обычно все объекты выделяются на куче? Тут у вас не будет выделения на куче в большинстве случаев, ведь есть оптимизация хранения, то есть аллокаций меньше, а не больше

И на каждый писать класс обёртку? Отлично.

Не на каждый класс, а на каждый полиморфный метод, при этом методы можно переиспользовать при создании других полиморфных интерфейсов

Да посмотрел, дикое извращение лишь бы не использовать vtable

Нигде не было заявлений об отсутствии vtable или о том, что я пытался её не использовать. Суть в отказе от ключевого слова virtual и проблем которые оно приносит

Вам напомнить, что при использовании виртуальных функций обычно все объекты выделяются на куче? Тут у вас не будет выделения на куче в большинстве случаев, ведь есть оптимизация хранения, то есть аллокаций меньше, а не больше

Вызов конструктора копирования\перемещения никуда при этом не девается. Всё равно оверхед больше чем при копировании указателя.

Не на каждый класс, а на каждый полиморфный метод, при этом методы можно переиспользовать при создании других полиморфных интерфейсов

Я это и имел ввиду, вы же понимаете насколько это неудобно?

Суть в отказе от ключевого слова virtual и проблем которые оно приносит

Честно говоря за 12 лет работы, ни разу не сталкивался с проблемами из-за virtual при том что работаю с графикой

вы можете сделать методы, которые будут принимать указатель и поставить размер буфера sizeof(void*), таким образом у вас никогда не будет аллокаций и копирование / мув будут происходить для указателя

Плюс в библиотеке ещё не документировано есть polymorphic_ptr<Methods...>, который отделяет полиморфность от типа и практически аналогичен void* для копирования(и принимания в интерфейс соответственно как в случае с виртуальными функциями)

Под GraphicObject::move имелся ввиду просто метод перемещения графического объекта, с move-семантикой это никак не связано.
А за разъяснение спасибо. Идея в целом понятна, просто городить для каждой функции отдельный класс со статическим методом многословно и не очень удобно.
На мой взгляд тему реализации динамического полиморфизма с семантикой значений хорошо раскрыл Louis Dionne в своей библиотеке https://github.com/ldionne/dyno. Но там тоже, без средств вменяемой кодогенрации, получается жутковато.

Получается вы создали псевдотаблицу виртуальных методов.

Самое интересное в оригинальных виртуальных методах это вызов базовым классом виртуального метода наследника.

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

class Base {
public:
    virtual void foo() { std::cout << "Base::foo" << std::endl; };
    void bar() {  std::cout << "Base::bar" << std::endl; }
    void baz() {  foo();  bar(); }
};

class AB : public Base {
public:
    void foo() { std::cout << "AB::foo" << std::endl; }
    void bar() { std::cout << "AB::bar" << std::endl; }
};

class DAB : public AB {
public:
    void foo() { std::cout << "DAB::foo" << std::endl; }
    void bar() { std::cout << "DAB::bar" << std::endl; }
};

int main( int argc, char * argv[], char * env ) {

    DAB tAB;
    tAB.baz();

Сейчас мой код обработки матриц на Java работает быстрее чем идентичная по коду реализация на C. Это факт. Не в разы, но на 10-15%. Оптимизатор работает лучше, ему не надо подсказывать или писать ассемблерный код.

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

Тут стоит заметить, что Ваши матрицы не единственное мерило. Расскажите как на jvm для linux в одном бинарнике сделать менеджер управляющий своими воркерам(процессы) с тредами, с возможностью оперативно уменьшать/увеличивать кол-во воркеров и с релоадом без разрыва соединений, на манер как это реализовано в nginx например. Отвечу сразу - никак, кроме запуска пачки jvm.
Вообще-же jvm в качестве ОС (а не поверх ОС) - весьма интересное решение.

Легко... Это называется GraalVM делаем бинарник, можно вообще в posix напрямую ходить. А можно ещё взять кваркус и vert.x под капотом и добавить реактивщины на все это, с воркерами, эвент лупами и прочим...

JVM в качестве ось - это глупость, а вот хождение прямо в ось(да даже в регистры процессора) уже реальность

Неа, не получится. В graalvm:

  • Directly using process related syscalls like clone, fork, vfork, etc. is not supported.

  • The exec function family is not supported.

Ну и про глупость - java me squawk, намекают как-бы, ну и есть те кто неплохо на этом зарабатывают: https://microej.com/

В graalVM вы можете на прямую ходить в Линукс и делать что хотите. POSIX доступен, ровно как на сях. Просто в доках этого нет ещё граалевских.

Просто повторюсь, я спокойно ддергал mmap и управлял регистрами прямо из java и Грааль.

То о чем вы говорите - это netty+vert.x+quarkus.... Просто никому не интересно 2й nginx писать... Зачем?

Вот как в graalvm обойдёте явно указанные ограничения, тогда и поговорим о доступности POSIX "ровно как на сях".
И с чего это никому не интересно писать второй nginx - куда ни посмотри, а на любом популярном языке не меньше десятка реализаций web-сервера или прокси или фреймворка.

А давайте поговорим...

import org.graalvm.nativeimage.c.function.CFunction;
import org.graalvm.nativeimage.c.function.CLibrary;


@CLibrary("c")
public class Sample {
    @CFunction(transition = CFunction.Transition.NO_TRANSITION)
    private static native int fork();

    public static void main(String[] args) {
        int result = fork();
        System.out.println("result: " + result);
        while(true) {
            try {
                Thread.sleep(1000);
                System.out.println("process result = " + result);
            } catch(Exception e) {}
        }

    }

}

Компиляем, запускаем

javac Sample.java
native-image Sample
./sample

и видим:

result: 533
result: 0
process result = 533
process result = 0
process result = 533
process result = 0
process result = 0
process result = 533
process result = 0
process result = 533
process result = 533

Заглядываем в top и видим:

 PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
    1 root      20   0     892    484    416 S   0.0   0.0   0:00.13 init
    8 root      20   0     892     88     20 S   0.0   0.0   0:00.00 init
    9 root      20   0     892     88     20 S   0.0   0.0   0:00.12 init
   10 sanders   20   0   10268   5384   3420 S   0.0   0.0   0:00.42 bash
  531 sanders   20   0   98932   8860   5492 S   0.0   0.1   0:00.09 sample
  533 sanders   20   0   98932   4320    936 S   0.0   0.0   0:00.00 sample
  534 root      20   0     892     88     20 S   0.0   0.0   0:00.00 init
  535 root      20   0     892     88     20 S   0.0   0.0   0:00.00 init
  536 sanders   20   0   10032   5164   3424 S   0.0   0.0   0:00.09 bash
  549 sanders   20   0   10868   3664   3152 R   0.0   0.0   0:00.02 top

Видимо из java сделать таки можно.

Соответственно ваше утверждение - является ложным

Update: Судя по всему вы в доки по llvm смотрели, а не про native компиляцию, отсюда и не верные выводы.

Да я смотрел доки по llvm. И да native-image позволяет сделать форк, оказывается в 2019 году свешилось, алилуя!
Однако:

$ time /usr/lib/jvm/java-16-graalvm/bin/javac Sample.java && time /usr/lib/jvm/java-16-graalvm/bin/native-image Sample

real	0m1.084s
user	0m1.730s
sys	0m0.191s
[sample:1115840]    classlist:   1,441.24 ms,  0.96 GB
[sample:1115840]        (cap):     840.95 ms,  0.96 GB
[sample:1115840]        setup:   3,438.19 ms,  0.96 GB
[sample:1115840]     (clinit):     374.36 ms,  1.74 GB
[sample:1115840]   (typeflow):   6,396.07 ms,  1.74 GB
[sample:1115840]    (objects):   5,129.39 ms,  1.74 GB
[sample:1115840]   (features):     488.59 ms,  1.74 GB
[sample:1115840]     analysis:  12,691.96 ms,  1.74 GB
[sample:1115840]     universe:   1,009.62 ms,  1.74 GB
[sample:1115840]      (parse):     868.96 ms,  1.77 GB
[sample:1115840]     (inline):   1,460.33 ms,  2.33 GB
[sample:1115840]    (compile):  13,060.04 ms,  2.38 GB
[sample:1115840]      compile:  16,215.19 ms,  2.38 GB
[sample:1115840]        image:   2,244.14 ms,  2.38 GB
[sample:1115840]        write:     317.16 ms,  2.38 GB
[sample:1115840]      [total]:  37,670.41 ms,  2.38 GB
# Printing build artifacts to: /home/rnz/experiments/graalvm/sample.build_artifacts.txt

real	0m38.611s
user	3m44.714s
sys	0m4.161
s
$ du -hs --apparent-size ./sample 
11M	./sample

Тоже самое на С/С++:

$ cat sample.cpp 
#include <iostream>
#include <chrono>
#include <thread>
#include <unistd.h>

using std::cout;
using std::endl;

int main()
{
  pid_t rc;
  rc = fork();
  if (rc == 0) {
    cout << "parent: " << getppid() << endl;
    cout << "child: " << getpid() << endl;
  }
  while (true) {
       std::this_thread::sleep_for((std::chrono::seconds(2)));
       if (rc == 0) {
         cout << "child: " << getpid() << endl;
       } 
  } 
  return 0;
}

$ time g++ -o cppsample sample.cpp

real	0m0.525s
user	0m0.463s
sys	0m0.060s

$ du -hs --apparent-size cppsample
23K	cppsample
$ strip cppsample && du -hs --apparent-size cppsample
15K	cppsample

Ну и потреблянство сравним тоже:

$ pmap -x $(pgrep sample) | egrep 'Address|total'
Address           Kbytes     RSS   Dirty Mode  Mapping
total kB           21488    6712    1320
Address           Kbytes     RSS   Dirty Mode  Mapping
total kB           21488    4764    1320

$ pmap -x $(pgrep cppsample) | egrep 'Address|total'
Address           Kbytes     RSS   Dirty Mode  Mapping
total kB            5976    3060     192
Address           Kbytes     RSS   Dirty Mode  Mapping
total kB            5976    1804     200

В целом неплохо, на мышах. Но ждать ~4 минуты сборки для минимального приложения, да ещё ~2,4G ОЗУ отъело - так себе удовольствие (на Intel i5-8250U). Ну и судя по доке у native-image тоже хватает ограничений, типа невозможности подгрузить динамические штуки, есть какие-то проблемы с сериализацией, ну и если по коду попадаются какие-то особенные функции и методы, то всё равно надо положить рядом jvm.

Но ждать ~4 минуты сборки для минимального приложения

Ну давайте ближе к реальности. 4 минуты надо собирать в финале. Гонять можно в JVM. А так да, native сборка - это можно Войну и Мир прочитать.

Ну и судя по доке у native-image тоже хватает ограничений, типа невозможности подгрузить динамические штуки

Не не возможности подгрузить, а сделать дополнительное приседание. Которое кстати уже делается самим граалем через agent и генерирует эти приседания.

 есть какие-то проблемы с сериализацией

Да нету их. Даже в 22.1 сериализацию лямбд прикрутили. Сейчас вот с ребятами из редхата(который Quarkus) делаем профайлер для реактивных приложений с разворачиванием лямд, эвент-лупов, воркинг пулов и всех методов в них.

ну и если по коду попадаются какие-то особенные функции и методы, то всё равно надо положить рядом jvm

Зачем? Собираете с флагом -static или -staticWithLibC и счастье вам.

Вообще, если костылей не хотите, то Quarkus вам в помощь. Туда уже многое затащили. По зависимостям, если не изменяет склероз - больше 1500 артифактов приходят. Буквально на прошлой неделе Алексей мерял.

Странно предъявлять языку, в основе которого лежит платформонезависимость - неумение работать напрямую с платформой.

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

Уже снимают это ограничение. Java = Linux в современном мире в подавляющем количестве случаев. Хочешь платформонезависимость  - она есть. Хочешь под платформу - тоже есть. Чуть выше можно увидеть пример в моем комментарии в ответ "что якобы нельзя, но можно"

Не "хочешь", а надо или не надо.
Вот "платформонезависимость" - не надо. Своей тонны java кода нет, что-бы пытаться не переписывая получить профит в производительности и эффективности. А начинать писать на java, то что можно эффективнее и оптимальнее реализовать на C/C++ - лишено смысла, кроме как в случае, если вокруг одни java-программисты и ты один со своим cpp.

Вот "платформонезависимость" - не надо.

Поясните мысль. В моей картине мира - вся "платформонезависимость" java - это Idea от JetBrains. Больше как на Linux серверах я яву даже близко последние лет 10 не вижу. То есть "платформонезависимость" - не более чем "мем из 90х". "платформонезависимость" есть, явы там нет.

А начинать писать на java, то что можно эффективнее и оптимальнее реализовать на C/C++

Я тут ниже начал тему раскрывать. Главный вопрос - "а что есть эффективность и оптимальность"? Я с огромным уважением отношусь к Джону Кармаку и его годам оптимизаций в ID. Но зачастую этот "эффективность" = "выдать быстрее заказчику" и "оптимальность" = "сделать дешевле". Иначе вы просто пролетите в тендере. Нахрен вы никому не нужны, если готовы за 5 лет круто, а не за год.

Далее - разговоры C++ или Java по моему вообще бессмысленные. Начнем с того, а есть ли у вас вообще java программисты, чтобы на ней писать (для С это тоже верно). Нет, так чего тут разговаривать.

А дальше мы начинаем упираться в задачу... Что лучше, ради "оптимальности" плодить дикие связки C++/JNI/Java или все же сделать на одном языке. Железо ныне сильно дешевле труда программиста, его можно увеличить в 2 раза часто за зарплату одного из них.

Короче говоря - борьба С++ или Ява (как и многое другое) это борьба остроконечников и тупоконечников, как в известной книге. Первична задача. Дальше классический треугольник: быстро-дешево-качественно. Наличие специалистов, риски, цена, сроки, стоимость и сложность багофикса и поддержки. А только потом средства его достижения, как раз где и находится язык.

Больше как на Linux серверах я яву даже близко последние лет 10 не вижу.

Андроид тихо засопел в уголке.

Слота то я и не приметил... Или забыл...

Правда не изучал онное, но если не изменяет склероз, то там по моему с 1.8 ещё не выбрались...

Кстати, разговаривал с соседями, это шишки SW в Samsung/Broadcom/Qualcomm - массово софт на golang с плюсов переводят.... Язык конечно хорошо, но стоимость разработки важнее

Про "плаформонезависимость" - непонял о чём вы, но я имел ввиду, "способность" програм для jvm работать на любой ОС где работает jvm без изменений под конкретнуюю ОС, на практие оно только на десктопах и годится.
Про эффективность, я вам уже привёл пример: ScyllaDB.
Про "есть/нет программисты" - неверно.
Есть бюджет, что-бы:
а) нанять программистов
б) оплатить эффективное решение.
Бюджетом можно распорядиться рационально, а можно разбазарить.
300 разрабов собравшихся вокруг биллинга - разбазаривание бюджета.
40 инстансов субд на java (в облаке-ли, on-premise-ли), там где справятся 3-5 инстансов субд на cpp - разбазаривание бюджета
И вопрос только в том, разберёт "заказчик музыки", что ему впарили "какофонию" или нет. Увы, но часто, выбор эффективного языка, вообще не стоит в начале, пишут не оценивая будущее TCO, в результате имеем кучу легаси с тремя сотнями разрабов плящущими вокруг и доказывающими что "хуяк-хуяк и в продакшин" это time-to-market.

Больше как на Linux серверах я яву даже близко последние лет 10 не вижу

посмотрел, из установленных пакетов у меня от jre зависят dbeaver и sweethome3d. пусть это и linux, но, всё-таки, десктоп.
из установленного не из репозитория, помимо idea, пользуюсь программками для управления kvm (подключение к видеовыходу удалённого сервера). в свежих серверах помимо java-приложения есть html5, но есть и старые сервера, и внешние kvm, где только через jnlp.


а ещё у нас кассиры на кассах работают с java-приложением. да, там тоже linux, но, судя по документации, работа под windows тоже поддерживается, как бэкенда, так и фронтенда.


это то, что пришло в голову за пять минут.

Во-первых, я предъявлял не языку, а jvm. Во-вторых, эта кроссплатформенность вообще не упёрлась.
И предлагая задачу, я намекал на то, что jvm не позволит сделать ряд вещей, хотя как оказалось с 2019 года есть возможность запилить native приложение на java (graalvm), однако выглядит это всё равно как срещивание ужа и ежа, и со значительным оверхедом, хотя в целом неплохо в сравнении c jvm.
В общем я о том, что когда нужна реальная производительность и эффективность, то выбрав java с jvm, можно долго ломать копья и всё равно не достич приемлемых показателей, и на этом фоне кроссплатформенность по типу jvm идёт лесом. Показательными примером является Cassandra vs ScyllaDB

Когда речь заходит про "реальную производительнось" не следует забывать про тип задач и количество программистов в нем. Если это какое то небольшое приложение - да, вы совершенно правы. А если это скажем риалтайм биллинг, который пишут 300 человек, то производительнось встаёт на 2й план после стоимости программистов. То есть своей задачи свое решение. Тут и производительность и time to market и главное цена. Одна и та же система, но сделанная "по дзену" но в 100 раз дороже никому на рынке не будет нужна

Та же система "по дзену", а не как обычно "кто во что горазд, а потом скручиваем изолентой" потребовала бы не более 30 человек, в 10 раз меньше времени на разработку и в 10 раз меньше серверов.

Назовите мне хоть одну IT компанию, у которой 30 сеньеров сидят ровно на 5й точки и ждут проекта такого объема на кассандре, вместо того чтобы не испытывать недостаток ресурсов, нанимать народ и прочее-прочее прочее. В идеальном придуманном мире - это возможно. В реальном - в крайне редких и исключительных случаях.

Извините, но кто мешает сделать JVM реализацию не для ОС а для железа? Никто не мешает. https://ru.wikipedia.org/wiki/JavaOS
Просто это очень неоптимально с точки зрения разработки -- малейшее изменение платформы и нужно переписывать JVM. С точки зрения программы на Java -- вообще ничего не поменяется, и ваши низкоуровневые задачи управления процессами и другими ресурсами будут отражены в абстракциях пакетов.

Да никто не мешает, пусть кому надо тот и делает. Мне не надо.
"малейшее изменение платформы и нужно переписывать JVM" - ну как-бы с ОС именно так. Ведь основная фишка jvm: абстрагировать, вируализировать, эмулировать - ну и логично тогда уже сразу вместо ОС применять jvm.

Да нет смысла в этом... Особенно для java + graal. Оно ж заточено куда - в облако. На 10мб alpine работает.

Это всякие извращенцы типа меня делают embedded to cloud. В большинстве случаев это не нужно

UFO just landed and posted this here

А её ещё попробуй напиши.

UFO just landed and posted this here

но MKL это разновидность BLAS+LAPACK и быстрее он только на Intel

Хотя конечно сам по себе хорош

В том то и дело. Что на С нужно самому извращаться с платформой делая код на переносимом С оптимальным по производительности, тогда как на Java все извращения и оптимизации делает разработчик JVM.
И я не уверен что я это сделаю лучше, т.к. JVM лучше знает как расположить в памяти массивы, оптимизировать обращения с учётом кэширования и ещё 100500 зависимостей.

И да, я не фанат Java, и долго писал на С/С++ ассемблере, и считал что это самые быстрые реализации, но Graal меня удивил.

Чистая Java конечно медленнее чем С на 15-20%.

Код вычислительный без выделения памяти и классов.

Я сомневаюсь что ваши версии переплюнут тот же https://bitbucket.org/blaze-lib/blaze/src

Чтобы писать действительно быстрый код без знания платформы пока не обойтись.

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

Вы явно что-то не то на С написали. Покажите ваш код)

UFO just landed and posted this here

а давайте ради интереса проверим.

Скиньте полный класс для теста + как его компилять. А я перепишу "в лоб" на яву и компильну в бинарник под graal.

Соответственно обменяемся исходниками и проверим на разных конфигурациях.

Тут все ждали код на сях, а тут java :) завтра под Граалем запущу, интересно цифры будет в jvm vs native увидеть

Обновил

Test time : 1517
Result : -1.000000 -2.696002

UFO just landed and posted this here

Майкрософт реализует clock_t как long, 32-разрядное целое число со знаком, а макрос CLOCKS_PER_SEC определяется как 1000. Времена 18.2 cps давно прошли ;)

printf( "Test time : %lf\n", ( end - start ) / (double)CLOCKS_PER_SEC * 1000. );
Test time : 1535.000000
Result : -1.000000 -2.696002

Код на С упрощён, я не стал приводить реальную реализацию. И там присутствуют некоторые неприятные для С вещи.

  1. массивы в Java проверяют выход за границы.

  2. массивы переданные в функцию не имеют заранее известной размерности aka [ 32 ][ 32 ] и компилятор не может вставить константы смещений в код вычисления индекса как это делает компилятор С

  3. подозреваю, что отсутствует выделение памяти как в clone() перед каждым вызовом, а не просто копирование memcpy

Думаю, что введение этих ограничений приведёт к падению производительности.

graal мне ставить лень, но

Чистая Java, как у вас, медленнее graalvm.

UFO just landed and posted this here

[...] которые jit вполне может вырезать.

Не может. Это нарушает контракт обращения к массиву по индексу.

Опции JVM: -XX:+UseG1GC

UFO just landed and posted this here

Если он может себе доказать, что индексов больше 31 не бывает

Я уже написал, это не реальный код а кусочек, и n там вполне меняется, как и размеры массивов.

Поэтому никто ничего заранее доказать не может.

Однако со всеми оптимизациями Java справляется с кодом не хуже С++. А если сделать код С++ исключительно надёжным то и превосходит.

UFO just landed and posted this here

Ну если вы внимательно смотрели то индесксы массива тоже снаружи приходят. Тут не отделаешься одной проверкой в начале.
Java это делает на каждое обращение, и делает это быстрее С++. Следовательно, код на Java -- надёжнее.

А какой кодогенератор у вас там используется? clang-based или что ещё?

Clang:

Test time : 1237.000000
Result : -1.000000 -2.696002

[...] которые jit вполне может вырезать.

Добавил туда же реализацию на С++: std::vector, с проверкой на выход за границы и отсутствием явного размера:

PS: std::array не подходит, поскольку требует явного указания размера.

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

UFO just landed and posted this here

Не понял, а здесь у вас что?

Этот код не вызывается, см. main.cpp

UPD: Это С реализация, я же написал про С++

sanders@ubuntu:/mnt/d/work/JavaVsC$ java --version
openjdk 17.0.3 2022-04-19
OpenJDK Runtime Environment GraalVM CE 22.1.0 (build 17.0.3+7-jvmci-22.1-b06)
OpenJDK 64-Bit Server VM GraalVM CE 22.1.0 (build 17.0.3+7-jvmci-22.1-b06, mixed mode, sharing)

sanders@ubuntu:/mnt/d/work/JavaVsC$ java Main
Test time : 1427
Result : -1.0, -2.696002453891236

sanders@ubuntu:/mnt/d/work/JavaVsC$ native-image Main
sanders@ubuntu:/mnt/d/work/JavaVsC$ ./main
Test time : 1883
Result : -1.0, -2.696002453891236

sanders@ubuntu:/mnt/d/work/JavaVsC$ clang --version
clang version 10.0.0-4ubuntu1
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
sanders@ubuntu:/mnt/d/work/JavaVsC$ clang -O3 -march=native MainC.c -o main && ./main
MainC.c:6:8: error: unknown type name 'bool'
static bool lu_factor( double a[ 32 ][ 32 ], int n, int ipvt[46] ) {

native - оказался медленнее. Почему - надо изучать.

си - не компильнулся :(

Чистая Java, как у вас, медленнее graalvm.

Это спорное утверждение. Например в graalCE - SerialGC только. А вот в EE - уже пожирнее + всякие фишки:

  • GraalVM Enterprise only: --pgo: a comma-separated list of files from which to read the data collected for profile-guided optimization of AOT compiled code (reads from default.iprof if nothing is specified).

В "чистой java" еще несколько штук. Тут от задач и тюнинга зависит

си - не компильнулся :(

Попробуйте собрать в режиме С++.

JS это хорошо, но код надо вызвать 1М раз, при этом нужно массивы копировать. Псевдокод:
a = A
b = B
ipvt = IPVT
call lu_solve( a, n, ipvt, b )

Автоматика говорит, что достаточно и пары тысяч вызовов.

А копировать массивы тут не нужно. Вам тоже советую убрать лишнее копирование.

А копировать массивы тут не нужно.

В оригинальном коде требуется брать как бы "чистые" массивы и "портить" их этим вызовом.

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

Автоматика говорит, что достаточно и пары тысяч вызовов.

Ну это же тестирование производительности. В предоставленных сниппетах должно быть вычисленно 1М вызовов.

То есть вы не понимаете, что делает этот код и какие массивы он "портит"?

Это вы похоже не понимаете для чего был написан этот пример.

Ваш опыт оптимизации примеров оставьте при себе, спасибо.

Ну так разъясните, какая сакральная цель копировать массив, чтобы передать в функцию, которая его не меняет.

Т.е. смысл вызывать функцию, результаты которой никому не нужны вас не смутил. Ок.

Копирование массивов нужно для приближения теста к реальным условиям вызова данного кода.

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

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

Либо раскомментируйте `lu_factor`, который меняет остальные массивы, либо уберите их копирование, раз уж мы тут говорим про эффективность и реализм. А тестировать заведомо бестолковый код - не имеет смысла.

в ваших реалиях массивные блоки копируются по поводу и без.

либо уберите их копирование, раз уж мы тут говорим про эффективность и реализм.

Я вам уже предлагал свои огромные скиллы оптимизации тестовых кодов оставить при себе, досвидания.

А если прогревать JVM?

Добавил много итераций:

Hidden text
$ java Main
iteration : 0
Test time : 1403
Result : -1.0, -2.696002453891236
iteration : 1
Test time : 1273
Result : -1.0, -2.696002453891236
iteration : 2
Test time : 1253
Result : -1.0, -2.696002453891236
iteration : 3
Test time : 1212
Result : -1.0, -2.696002453891236
iteration : 4
Test time : 1323
Result : -1.0, -2.696002453891236

Процентов на 10 лучше, и то не всегда. Дисперсии не измерял.

А покажите ключи оптимизации?

Например без --ffast-math внезапно векторизации флоат операций не произойдет.

с плохой производительностью

Какие языки из индекса-TIOBE (но можете взять и любой другой топ-20 популярных ЯП на свой вкус) обладают хорошей производительностью? Хочу понять на чём имеет смысл писать в 2022 году.

"Набор классических ошибочных стереотипов о Java начинающего программиста" из палаты мер и весов прямо-таки.

Сэр, мне кажется, но вы просто не умеете готовить эти ЯП (C++, Java). Тут у меня два соображения:
1. Ваши примеры "плохого С++" выглядят как примеры "не целевого использования". Вы просто пытаетесь писать простые вещи с использованием сложного функционала. так делать нетсмысла.
2. Обратите внимание какие ЯП используются на топовых соревнованиях по спортивному программированию. Там производительность крайне важна! И организаторы выбрали C++ и Java. Вам не кажется, что люди, которые годами учились писать высокоскоростной код несогласны с тем, что Java имеет плохую производительность? Можете лучше (производительнее)? Покажите на каком-нибудь top coder.

Как человек который участвовал в топовых соревнованиях по спортивному программированию, хочу сказать что Java используется исключительно для реализации задач на длинную арифметику. В каждой команде есть как минимум 1 человек, который даже если и не умеет полноценно в Java, то всё равно может написать короткую функцию, которая условно просуммирует BinInteger в цикле и потом его выведет.


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


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


Тот же C# сейчас сильно упарывается что бы завезти много фич для повышения комфорта низкоуровневого программирования, но проблема в том что весь язык (как и Java), с ног до головы спроектирован в пользу удобства. И для оптимизации нужно отдельно изощряться, там где в С++ всё происходит само собой.

А что вы не можете сделать с битами в обычной структуре? Причем тут юнион?

union bsshort {
    unsigned short val;
    struct {
#ifdef LITTLE
        char l;
        char h;
#else
        char h;
        char l;
#endif
    };
    char bytes[2];
}

Например...

и что мешает вам использовать в этой ситуации типа похожий на span, который даёт вам доступ кроссплатформенно к младшему байту при обращении по индексу [0] ? Неужели легче везде расставлять ифдефы?

UFO just landed and posted this here

 type puning трюков с union нарушает active member rule

А я всю жизнь думал, что тип union придуман ИМЕННО для таких трюков.

UFO just landed and posted this here

Давно не лазил в стандарт, но там везде в таких случаях были исключения для POS (plain old struct), так что мне кажется что конкретно этот bsshort по прежнему совершенно легален и определен. Могу быть не прав конечно, но ооочень удивлюсь.

UFO just landed and posted this here

Да, согласен, тоже глянул и поразился. Удивительно, но даже в случае С-совместимого кода гарантируется нормальный layout, но толку в этом ноль, компилятор может делать что хочет если идёт доступ не к тем полям что присвоены.

В java тоже есть эти устаревшие массивы, местами несовместимые с generic'ами, да ещё и индексируются они только int'ом, а не long'ом. Если нужны структуры с 2^31 элементами и более, их только руками реализовывать.

Долго думал, так и не придумал.... Покажите пожалуйста use cases где нужны массивы с 2^31 в реальной жизни 99,9% программистов.

а) воксельные модели NxNxN, где N чуть более 1000.

б) файлы размером чуть более 2ГБ (имеют или не имеют отношения к п. а).

в) 2d изображения немногим больше 500 мегапикселей (не сталкивался).

А теперь попробуйте найти кейс, когда это не потоковое действие, а надо обязательно целиком в память засосать, чтобы начать работу. Получим ровно что я написал про 0,01% случаев, когда это надо.

Но даже тут, можно обойтись другими средствами, а не массивом.

Ээ.. Случайный доступ к таким данным?

Конечно, можно обойтись системой кэширования или многомерными ссылочными массивами, но скорость несколько уменьшится. Было бы проще просто работать с обычными массивами, железо-то позволяет. Вон, в .net массивы индексируются long, да и во многих других языках. Особенно обидно, когда файл (массив) не так уж и велик, но слегка в 2^31 не влезает. И довольно неприятно, когда у пользователя (условно) при N=1290 программа ещё работает, а при N=1291 перестаёт. Приходится предусматривать эту возможность, писать странный код.

Такие объемы вы уже вряд ли в памяти будете хранить. Ибо и памяти на пару объектов хватит только и врятли "просто случайный доступ" это бизнес кейс.

У вас уже он где то будет валятся - а дальше что-то типа https://docs.oracle.com/javase/7/docs/api/java/nio/channels/FileChannel.html#map(java.nio.channels.FileChannel.MapMode, long, long)

И вперёд...

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

Mmap на jvm, помнится, когда-то приводил к утечкам виртуальной памяти. На таких размерах файлов и все 256ТБ виртуальной памяти съесть недолго. Пока наиболее интересный вариант - MemorySegment, наиболее неприхотливый - кастомные структуры из ссылочных массивов.

Вот просто по памяти:

sun.misc.Unsafe.allocateMemory(long bytes)

jdk.incubator.foreign.MemorySegment (эта часть уже стабильна)

Если говорим про GraalVM:

org.graalvm.nativeimage.UnmanagedMemory.malloc

Ну и в native можно прямо в POSIX:

@CFunction(transition = CFunction.Transition.NO_TRANSITION)
public static native PointerBase mmap(PointerBase addr, long len, int prot, int flags, int fd, long off);

К сожалению, все эти решения требуют ручного управления памятью и имеют несколько нестандартный синтаксис. Что-то вроде С++ получается.

Именно так... для не стандартных кейсов не стандартные решения...

Меня печалит, что кейс "доступ к элементу массива (списка)" норовит стать нестандартным и невозможным (без написания адского кода), когда массив занимает всего 10-20 процентов от доступной памяти. Это искусственное сейчас ограничение. Да, раньше int хватало всем для индексации, но теперь-то long хватает всем.

Это как раз то что называют:
"Что только люди не делают, что бы не учить C++" ©

А давайте перефразирую: Работаешь с 64-х битными массивами - учи С++ :)

В реальности, по моему скромному мнению, массивы только в нише специфичных кейсов остались. 90% народа работает с листами, мапами, сетами и прочими обертками

А листы/мапы/сеты умеют в размер больше чем 2^31?
Я вполне могу представить, что на C++ кто то захочет массив/вектор булов размером больше чем на 500Мб. Но так же не вижу причин оправдываться на вопрос что в Java это нельзя сделать из коробки.


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


Поэтому тезис имхо очень подходит)

Ключевое написанное вами - "Я вполне могу представить".

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

Но нет же. Конечно у рефлекшна есть ряд задач, которые он решает более удобно, с этим никто и не спорит. Речь про то что у каждого языка есть свои преимущества и недостатки. Но сделать из Java замену C++ не получится даже близко. Как и из C++ замену Java.


Кстати про рефлекшн и аннотации (да и про GC). Тот же UE4 вполне успешно сделал их реализацию поверх всего этого поверх C++. Причём с предметной спецификой, когда не всё попадает в GC и рефлекшн. Так что это всё тоже можно сделать с определённым извратом.

Ну раз "Но нет же" и "у каждого языка свои преимущества", то это не согласуется с вашей фразой с которой начался этот разговор - "Это как раз то что называют:
"Что только люди не делают, что бы не учить C++" ©"

То есть я написал как решить эту задачу на Яве. Всего то показал опции.

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


"Но нет же" — потому что я совсем не согласен с тем что наличие в Java рефлексии хоть каким либо образом обесценивает возможность работы в С++ с большими массивами.

Если мы говорим, про "сферический длинный массив в вакууме", то вы безусловно правы.

Однако я не встречал компании которые пишут исключительно длинные массивы. То есть это какая то из задач. Если у нас компания и так пишет на С++, то нам нечего обсуждать.

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

То есть мы найдем сишниеа, возьмём на работу, выгоним нахрен через 3 месяца, если так как он не нужен больше или найдем контактора.

Дальше напишем кучу JNI, который надо будет так же как то поддерживать... Ну то есть "костыль на java"и ваш "просто взять с++" начинают играть совершенно новыми красками...

По этому я и написал про наиболее подходящий под задачу язык. Если задача например обработка изображений или рендер, то там всё не обойдётся только лишь длинными массивами. Там и множество других проблем всплывёт.


А вот если делать сервер, то Java/C# на порядок более подходящие языки. И даже если появится такая задача с массивами то можно её решить более простым способом, например сделав абстракцию с несколько массивами внутри, а не делать платформозависимый код.


Собственно я пока что не встречал компаний/проектов, где пишут исключительно на одном языке. Из того что я вижу, в рамках одного проекта вполне себе успешно сочетаются C#/C++/Java/Python/Powershell и местами немного Rust/Go.

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

И да, union был в С не для того что написано в вашем примере, а для работы с аппаратурой, битовыми полями

Сомневаюсь, что это так, ибо "C99 6.7.2.1-11:An implementation may allocate
any addressable storage unit large enough to hold a bit- field. If
enough space remains, a bit-field that immediately follows another
bit-field in a structure shall be packed into adjacent bits of the same
unit. If insufficient space remains, whether a bit-field that does not
fit is put into the next unit or overlaps adjacent units is
implementation-defined"

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

великолепном hole C++

Великолепная опечатка. Дыра++

C++ это феномен нашего времени – популярный язык, который позволяет писать вот реально «как хочешь», когда все остальные языки пытаются диктовать какую-то одну парадигму и строят все вокруг нее.
Потому я бы в C++ только добавлял – рефлексию там (и статическую и динамическую конечно), аннотации к полям и типам, деривативы, новую версию макросов (не удаляя старой конечно), процедурные макросы и так далее.

Вам не приходилось, получать код от других программистов, которые пишут "как хотят"? Поделитесь впечатлениями?

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

который позволяет писать вот реально «как хочешь»

В итоге даже Страуструп оценивает свое знание плюсов на 7 из 10, потому что знать этот язык более менее полно нереально. И это создатель языка, который посвятил ему десятилетия своей жизни, причем до тонны новомодных нововведений. Как теперь угадывать, какую часть C++ у которого только список терминов занимает страниц 20 A4 мелким шрифтом нужно учить, чтобы гарантированно понимать код который пишет хотя бы условное большинство - хз.

О, ООП опять хоронят.

10 - ключевое слово final (запрет наследоваться от типа) - не имеет ни одного известного мне полезного применения, ломает обобщённый код, вердикт - удалить

Отличный довод, просто великолепный. И не поспоришь ведь.

покажите пожалуйста полезное применение... Может это?

struct example final {
    virtual void foo() = 0;
};

Если мне не изменяет память, то прежде чем кейворд auto поменял своё значение, была просмотрена куча кода на предмет его использования. Немного разнится с личным мнением Васи из интернетов.

final позволяет компилятору делать оптимизации, вызов методов происходит непостредственно, без использования vtable, если переменная типа который final

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

Компилятор способен понять в пределах одной единицы трансляции, если же класс уже скомпилирован, то понять будет ли он в будущем унаследован в другой единице трансляции компилятор не может)

Захочет - сможет. Особенно учитывая тот факт, что девиртуализировать нужно по месту применения, то есть компилятор видит что создан конкретный тип и можно не делать вызов через vtalbe

Если функция принимает указатель на базовый класс с виртуальным методом и вызывает его. Чего вы там сможете девиртуализировать?

целое ключевое слово

final -- не ключевое слово, иначе этот код не компилировался бы:

#include <cstdlib>
#include <iostream>

int main() {
	struct final final {
		virtual ~final() final {
			std::cout << __func__ << std::endl;
		}

		virtual operator int() final {
			return EXIT_SUCCESS;
		}
	} final;

	return final;
}

это называется контекстно зависимое ключевое слово

это называется контекстно зависимое ключевое слово

Ключевые слова не могут быть использованы в качестве идентификаторов.

Я использовал здесь final два раза в качестве идентификатора (имя структуры и имя переменной) безо всяких проблем, и ни один из 4-х компиляторов даже не пикнул: https://godbolt.org/z/rKh169Wb3

Если же прочитать нечто, достаточно близкое к оригиналу:

https://en.cppreference.com/w/cpp/keyword

То выяснится, что final среди ключевых слов отсутствует.

Зато он присутствует среди идентификаторов с особым смыслом/значением, который (смысл, то бишь, или значение) они (идентификаторы) имеют лишь в определённых контекстах.
В другом месте https://en.cppreference.com/w/cpp/language/final он описывается как спецификатор.
И нигде он не описывается как ключевое слово.

Специально глянул драфт 2015-го года -- там final описывается в главе 2.10, которая называется Identifiers.

Вот пункт 2 оттуда:

2 The identifiers in Table 2 have a special meaning when appearing in a certain context. When referred to in the grammar, these identifiers are used explicitly rather than using the identifier grammar production. Unless otherwise specified, any ambiguity as to whether a given identifier has a special meaning is resolved to interpret the token as a regular identifier.

Table 2 — Identifiers with special meaning

override final

Думаю, излишне будет уточнять, что Стандарт имеет силу аксиомы, то есть, он -- абсолютен, и если он указывает, что это -- identifier with special meaning, а не ключевое слово, значит, это абсолютно именно так и есть.

Напрашивается вопрос: зачем выдумывать про ключевые слова?

Прежде чем будет достигнута программистская зрелость, неизбежно придётся неоднократно взбираться на пик глупости кривой Даннинга-Крюгера.
То есть, само по себе, хоть это и неприятно, -- это необходимая издержка.
Но в дальнейшем -- всё определяется выбором, который делает человек, оказавшись там.
Выборе, сделанном для того, чтобы научиться не попадать туда больше, или для того, чтобы во чтобы то ни стало каждый раз "казаться правым".

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

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

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

Этот путь отнюдь не способствует достижению программистской зрелости.

Но возможность изменить свой выбор всегда остаётся.

UFO just landed and posted this here

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

UFO just landed and posted this here

С примерами у вас -- не очень.

С примерами контекстно-зависимых ключевых слов, которые могут быть использованы в качестве идентификаторов.

Именно ключевых слов, то есть, таких идентификаторов, в отношении которых в Стандарте прямо и недвусмысленно заявлено, что они "reserved for use as keywords" и/или "unconditionally treated as keywords".

https://eel.is/c++draft/gram#key-1.sentence-1

Следует прочесть немного выше о том, что мы вообще читаем:

https://eel.is/c++draft/gram#general-1.sentence-1
https://eel.is/c++draft/gram#general-1.sentence-2

А именно:

This summary of C++ grammar is intended to be an aid to comprehension. It is not an exact statement of the language.

Перевод отсюда:

Это краткое изложение грамматики C++ предназначено для облегчения понимания. Это не точное определение языка.

Суть той части документа, на которую вы сослались:

Это не точное определение языка.

И конкретно тот пункт, ссылку на который вы привели, противоречит следующей главе, находящейся в той части документа, которая является точным определением языка: https://eel.is/c++draft/lex.key

https://godbolt.org/z/K1ErnMMxe

class a { public: void foo() {} };
int main() {
    a a;
    a.foo();
};

Поясните, пожалуйста, что именно демонстрирует данный код.

UFO just landed and posted this here

Вы ведь понимаете, что это — вопрос определений, и что любой человек, примерно представляющий суть разговора, может понять, что «контекстно-зависимое ключевое слово» и «контекстно-зависимый идентификатор» — это одно и то же?

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

Проще говоря, переменную с именем class невозможно завести в принципе, потому что class -- это ключевое слово, и все такие идентификаторы в программе трактуются именно как ключевое слово class, и -- никак иначе, а переменную с именем final -- очень даже возможно, как раз по причине того, что final не является ключевым словом.

Более того, в Стандарте прямо указано, что в случае неопределённости, как интерпретировать контекстно-зависимый идентификатор, когда он может быть проинтерпретирован и так, и этак, выбор однозначно должен делаться в пользу обычного идентификатора, то есть, без специального значения (смысла).

Отсюда, в частности, следует, что невозможно определить безымянную структуру так, чтобы она стала final, потому что неопределённость, как интерпретировать final в этом контексте, разрешается, в соответствии со Стандартом, в пользу обычного идентификатора, а не в пользу специального значения (смысла) final, и поэтому следующий код успешно компилируется:

auto
fun() {
	struct final {
	} const s;

	return s;
}

struct s: decltype(fun()) {
};

Более того, я бы сказал, что «контекстно-зависимый идентификатор» субъективно имеет меньше смысла, чем «контекстно-зависимое ключевое слово», ну да ладно.

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

Для final -- это не так. Он -- доступен, причём, всегда.

В этом и была суть моего изначального замечания, потому что я полагал, что автор имеет ввиду именно "изъятие" идентификатора final из множества возможных имён для именования сущностей в программе.

А поделитесь плз ссылками на секту хоронителей ООП. Хочется поднять настроение.

final - пожалуй соглашусь с автором. Ещё с лохматых годов, со времён 90х и delphi, накрывался на финальные классы, которые приводили к дикому количеству копипасты. Реальных же случаев, когда без этого никак, на вскидку не припомню

Часто ООП хоронят в статьях об ФП. Но, всё же, теми, кто это ФП вот-вот попробовал и загорелся мысолью построить новый чудный мир, а всё прежнее отправить на свалку.

Автор описал язык ++ , без С. Имхо это офигенно само по себе.

Убрать union, typedef, массивы, void, int, макросы. И прощайте все заголовочные файлы C. Будем экспортировать нативные функции, как это делает C#.

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

Реальные проблемы в C++ выглядят не так. Вот так выглядят "классы" проблем, после фикса которых можно получить "C++ мечты":

  1. Вопрос о сломе ABI и всего веера положительных и отрицательных эффектов из-за этого.

  2. Бедность стандартной библиотеки по сравнению с буквально всеми другими языками (тут ABI ни при чем).

  3. Поехавший нейминг и правила, например овер9000 значений слова static при разных обстоятельствах; или что anonymous namespace наделяет все символы внутри себя internal linkage-ом (как до такого додумались?).

  4. Медленная компиляция по разным причинам.

  5. Отсутствие общепринятого менеджера пакетов/библиотек (тут могут быть разные мнения, но это тоже "класс" проблем)

  6. Достаточно медленное развитие языка в целом - те же концепты пытались родить с нулевых годов, а также медленная поддержка этого в компиляторах.

  7. Отсутствие встроенных линтеров и чекеров (которых овер9000, но ими пользуются не только лишь все, а только самые продвинутые).

А "фиксы", которые удаляют ООП и т.д. - это не решение всех проблем C++, а описание какого-то нового языка.

  1. Не вопрос стандарта языка, аби хранят реализации, а в стандарте такого понятия нет

  2. Зато есть куча другого кода вне стандарта

  3. Предложенное статье сокращает количество правил. Значительно

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

  5. Cmake

  6. Последние 12 лет очень бурное развитие

  7. Они не должны быть встроенными, потому что IDE и спелчекеры не должны являтся частью языка. Как и система сборки и документирования. Сторонние инструменты есть

Cmake это даже не решение, а само по себе проблема с синтаксисом из 70ых и без нормальной документации.

cmake это и не менеджер пакетов, это в первую очередь система сборки

*генератор скриптов для системы сборки make

*генератор скриптов для системы запуска команд оболочки в соответствии с DAG-ом правил и значениями атрибутов mtime у указанных файлов-целей. make — это даже не система сборки, строго говоря.

Не вопрос стандарта языка, аби хранят реализации, а в стандарте такого понятия нет

Ситуация из разряда "жопа есть, а слова нету". Почему нельзя удалить из стандарта ставший ненужным с C++11 метод push_back? (А точнее, переписать его код вместо создания нового метода emplace_back)

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

Я бы, кстати, еще AST (Abstract Syntax Tree) в стандарт "C++ мечты" легализовал, чтобы люди могли писать тулзы для исходного кода без привязки к внутренностям компилятора.

Зато есть куча другого кода вне стандарта

95% людей устроило бы наличие любой (даже не самой быстрой) json-либы (как в питоне). Если бы была нужна какая-то мега-быстрая json-либа (в realtime-приложениях), то там уже могут быть варианты, но свои трейд-оффы есть везде.

Предложенное статье сокращает количество правил. Значительно

Предложенное в статье главным образом создает какой-то совершенно другой язык.

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

Я имел в виду "класс" проблем, которые именно вызывают медленную компиляцию. Например, из-за некоторых свойств препроцессора тулза include-what-you-use в 10 раз толще, чем нужно быть, и все равно работает не совсем как следует. Значит, нужно порезать препроцессор. Просто не стал все это расписывать, иначе комментарий получился бы длиннее статьи.

Cmake

Многие считают это худшей системой сборки ever. Если приводить все аргументы, выйдет тоже немаленький текст, но я читал бомбежку на CMake (сам его редко использую) - меня впечатлило.

Они не должны быть встроенными, потому что IDE и спелчекеры не должны являтся частью языка. Как и система сборки и документирования. Сторонние инструменты есть

95% людей были бы согласны на любой нормальный спеллчекер в "C++ мечты". Да и что значит "не должны", "не могут". C#/Java/Python/Rust - у них все есть и они все могут.

ставший ненужным с C++11

Не понял, почему ненужным? Он позволяет создать объект на месте, но есть много случаев когда объект создается, с ним производятся манипуляции, и только потом он добавляется в вектор

Так можно добавить при помощи move- или copy-конструктора.

STL всосалась в стандарт когда JSON еще никому не нужен был, если вообще существовал. А теперь поздно что-то кардинально менять. Но вот оформить пропозал по включению JSON поддержки сейчас, никто не запрещает. Вот только С++ используется там, где текстовый, несамодокументированный формат передачи небольших, непотоковых данных немного редко применим. Так что вряд ли быстро примут в стандарт. Ну и без вменяемой рефлексии, это все будет полу-мерой без решения реальной проблемы, как получить по RPC готовый объект ничего о нем зная, или с динамическими полями.

Убирание push_back сломает не аби совместимость, а интерфейс совместимость))
Да и бывают ситуации когда он нужен

Насчёт стандартизации AST - я бы хотел стандартизацию интерфейса модуля. Кстати именно модули решат проблему инклудов...

А CMake обладает плохим синтаксисом, но в целом удобно, желаю всем на него перейти, чтобы подключать либы было удобнее(а синтаксис можно обернуть в программу на питоне по генерации скриптов CMake...)

Ну и что-что, а json либ громадное множество, а мне не хотелось бы ничего включающего в себя даже в названии JAVA SCRIPT в стандарте С++

а json либ громадное множество

Что является проблемой само по себе. Т.к. в проекте легко может появиться несколько библиотек, каждая их которых тянет разные либы для json. Это из той же серии, как раньше каждый фреймворк пилил свой класс строк, а потом их нужно было по пять раз переводить между собой на ровном месте.

UFO just landed and posted this here
UFO just landed and posted this here

синтаксис можно обернуть в программу на питоне по генерации скриптов CMake...

Уже обернули. Система сборки SCons это питоновский скрипт с многопоточным билдом объектных файлов и кешем компиляции (в модули до сих пор не может)

Поздравляю, вы изобрели язык D.

приятно, что человек думает самостоятельно, но конечно проще и чище делать new from scratch, совет простой - забыть "С++" и описать свой "D"

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

> и там все эти проблемы решены.

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

С помощью union можно сделать например так

typedef struct {

union

{

unsigned int address;

unsigned char bytes[4];

}

} ipv4_address;

И как в том же Java так же сделать? Возможно какие-то нормальные обходные пути и есть. Но я в Java максимум быдлокодер, таких не знаю, поэтому приходилось делать громоздкую конструкцию, где я при замене 32 битного значения вручную менял и массив и наоборот. А тут всё быстро и автоматически.

Естественно с первого раза конструкция не взлетела пришлось потом тратить время на вылавливание ошибок.

Вы написали UB. Если я правильно понимаю как вы это собираетесь использовать.

В Джаве так сделать нельзя и слава богу.

А зачем это делать в java или любом другом языке? Разделяем на разные типы и логически всем понятно кто-что-зачем-куда

а ещё это можно сделать без помощи union

(у вас int на другой платформе может быть 2 байта...)

И как это сделать в Java без помощи юнион?

У нас на порт приходит пакет 10 байтов. На самом деле же это числа по 2 или 4 байта + заголовок и контрольная сумма.

Есть конечно дуболомный вариант. Что если не самый старший байт <0 , то добавляем 256. И потом ещё умножаем на 256 в зависимости от того какой разряд. Но по мне , подход так себе.

----

Про два байта вы правы, такой код не универсальный, для универсальности надо делать костыли.

UFO just landed and posted this here

Почему бы не поправить стандарт?

UFO just landed and posted this here

Не увидел там ничего про "нельзя", только "в общем случае небезопасно", но этот общий случай проявляется лишь в вариантах (когда мы засовываем в него что попало), а там никому не надо читать не тот тип, что записан. Во всех остальных применениях юниона (структурирование буфера) там всё вполне безопасно.

UFO just landed and posted this here

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

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

UFO just landed and posted this here

Ближайшее, что есть для подробного в Java, это ByteBuffer.

Но позволяет читать один и тот же массив данных и как int и как byte

Спасибо , может мне пригодится. Интересно, как он внутри работает. В коде ByteBuffer.java описываются getFloat() и putFloat(float f) но самого кода , как это работает нет.

Единственное ,нашёл в модуле bits что-то вроде такого

static private int makeInt(byte b3, byte b2, byte b1, byte b0) { return (((b3 ) << 24) | ((b2 & 0xff) << 16) | ((b1 & 0xff) << 8) | ((b0 & 0xff) ));}

Примерно это работает вот так:

      SocketChannel channel = ...
      int bytes = ( volts.length + currents.length + resistances.length + 1 ) * Double.BYTES;
      bytes += Integer.BYTES * 7;
   
      ByteBuffer bbw = ByteBuffer.allocateDirect( bytes );
      bbw.order( ByteOrder.nativeOrder() );
      bbw.putDouble( t );
      bbw.putInt( callIndex ); // 0 - startIteration, 1 - doStep
      bbw.putInt( inCount );
      bbw.putInt( outCount );
      bbw.putInt( volts.length );
      bbw.putInt( currents.length );
      bbw.putInt( resistances.length );
      bbw.putInt( flags );
      bbw.asDoubleBuffer()
              .put( volts )
              .put( currents )
              .put( resistances );
      bbw.rewind();
      
      try {
         int sended = 0;
         while ( bbw.hasRemaining() ) {
            sended += channel.write( bbw );
         }
      } catch ( Exception x ) {
         disconnect();
         return;
      }

Самое зло с union - Это вызов его из Java. :) Год назад неделю промучался, чтобы вот это:

#define I2C_SMBUS_BLOCK_MAX	32	/* As specified in SMBus standard */
#define I2C_SMBUS_I2C_BLOCK_MAX	32	/* Not specified but we use same structure */


typedef union i2c_smbus_data
{
  uint8_t  byte ;
  uint16_t word ;
  uint8_t  block [I2C_SMBUS_BLOCK_MAX + 2] ;	// block [0] is used for length + one more for PEC
} i2c_smbus_data;


typedef struct i2c_smbus_ioctl_data
{
  char read_write ;
  uint8_t command ;
  int size ;
  union i2c_smbus_data *data ;
} i2c_smbus_ioctl_data;

в ioctl передать из java... Неделя в GDB и профайлера и вот счастье... Которое закончилось ровно 2 дня назад. Тот же самый код - уже не работает :)

Ждет нас опять чудное сравнение байтов в памяти...

Тоже игрался долго.

Без "ByteOrder.nativeOrder() "ничего не работало.

Код выше обменивался с программой на С по сети.

#pragma pack(1)
struct PICHeader {
   double time;
   uint32_t callIndex; // 0 - startIteration, 1 - doStep
   uint32_t inCount;
   uint32_t outCount;
   uint32_t volts;
   uint32_t currents;
   uint32_t resistances;
   uint32_t VccClk;
};

Попробую, спасибо.

Вопрос: как человек более близкий к ось и сям - не подскажите, есть ли лёгкий способ трэйсить что в ioctl пришло (на примере этих структур хотя бы)

На реальных данных наверно долго будете искать ошибку.

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

Если используете MSVC то упаковка структуры с выравниванием до байта делается #pragma pack(1).

Если вы используете GCC/Clang то:

Использование __attribute__((aligned(1))) сообщает компилятору, чтобы начать каждый элемент структуры на следующей границе байта, но не говорит, сколько места он может положить в конце. Это означает, что компилятору разрешено округлять struct до кратного размера машинного слова для лучшего использования в массивах и тому подобное. __attribute__((packed)) сообщает компилятору, что он вообще не использует никаких дополнений, даже в конце struct.

struct foo {

uint8_t bar;

uint8_t baz;

} __attribute__((packed));

А потом можно перекомпилировать этот код на машине с другим порядком байт в слове и "happy debugging, suckers"

Вы бы хоть uint32_t и uint8_t написали, что ли...

Хорошая статья. И провокационная, и по делу. Спасибо.


Про 1-8 пункты помечтать не вредно. Вот как Комитет разрешит ломать обратную совместимость, так можно и предложения официально оформлять.


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


Пункт 9. С++ он все таки объектный язык. Из-за нам тут машет Smalltalk, который ввёл моду на ООП, но шила в мешке не утаишь, и перегруженную грамматику языка все-таки как-то надо реализовывать, ключевое слово class для объявления объекта в целом оправдано. struct и class вдвоем избыточны да. Haskell вот вводит data, type и, внезапно, newtype слова. Ну что вообще не лучше class/struct и using/typedef. А Хаскелл трудно упрекнуть в неточности.


Пункт 10. С++ — язык общего назначения, в том числе и для хардкорных, узко-специфичных библиотек и фреймворков. И, теоретически, фреймворк может заставить пользователя не расширять какие-то иерархии. ADL опять же. Пересечение со статической типизацией и мета-программированием конечно возможно, но теоретическая необходимость в Стандарте С++ во многих местах прослеживается. Правило 3(5 с недавнего времени) тоже не в каждой базе кода встретишь, но мы же не удаляем из языка методы-операторы присвоения и равенства.


Пункт 11. Виртуальные методы — центр всей объектной модели объектно-ориентированного языка общего назначения. Rust предлагает динамические трейты, Java — вообще вся насквозь по умолчанию виртуальные методы использует. Я не углубился в реализацию предложенной библиотеки, и могу ошибиться в суждении, но только виртуальные методы на уровне языка умеют пересекать границы бинарника без рекомпиляции. Все шаблонные башни и чернокнижные преобразования, сломаются, если потребуется передать новые типы, неизвестные в момент создания библиотеки. (Этот факт, кстати, вызывает у меня несказанное удивление до сих пор. Не имея стандартного ABI С++ позволяет передавать указатели на интерфейсы через границы бинарников и это чаще работает, чем не работает. Хотя в принципе не должно работать вообще, по аналогии с проблемой манглинга имен и передачи STL сущностей через границу бинарника.)


Пункт 12. С++ — объектно-ориентированный. Опять. Метод, это не столько еще один блок кода, который вызывается вокруг некоторого инстанса. (Тут нам передает привет Go, где указатель на метод и на функции — одно и то же, а получатель вызова по указателю и по значению — тема для продвинутых и особо не афишируется). В С++ метод — это не столько операция над аргументами, сколько операция над объектом в первую очередь. В идеальном ООП мире не существует c = a.add(b), однако, шаблоны Visitor или Builder полагаются на то, что методы выполняют не только работу над аргументами, но и над внутренним устройством типа. Ну и RAII без методов было бы ущербным. Нам опять передает привет Go, где потоко-безопасный множественный доступ в коллекцию из разных функцию — антипаттерн в пользу каналов или унылого бойлерплейта с мутексом и кастомным типом вокруг каждой коллекции. То, что метод в С++ неотъемлемая часть типа, это и ограничение и преимущество. И просто так избавляться от в целом невредного инструмента ну как-то импульсивно. ADL вот только для функций используется, и это прекрасно. Хочешь применить алгоритм к аргументам — используешь свободные функции, хочешь запускать сложную машинерию скрыто от глаз вызывающего кода(и для хитрого продакшен кода, и для тестов, почему нет) — методы наше все.

Так методы фактически остаются, но понятие метод и указатель на метод исчезает, deducing this показывает что это вполне реальность

Насчёт пункта 11 - не то чтобы это должно было волновать С++ программистов, ведь длл вообще не существуют для С++. Но библиотека их(надеюсь) поддерживает, правда с дефайном (#define AA_DLL_COMPATIBLE ), можете посмотреть как оно реализовано(в стандартной библиотеке +- также работает междллное взаимодействие, насколько я знаю)

в Rust, на мой взгляд, крайне сырые и неудобные правила именования стандартных сущностей <...>

Можете пояснить, что вы имеете в виду?

C++ STL использует snake_case и преимущественно полные или достаточно длинные идентификаторы: std::shared_ptr; std::swap; std::in_place_index_t; std::format_to_n; std::unordered_multiset; std::condition_variable.
Комбинация типов через метапрограммирование хоть и монструозна, но псевдонимится через using, оставляя основное определение многословным, но однозначным. std::shared_ptr<std::variant<std::vector<std::string>, std::map<std::byte, std::string>>> не оставляет разночтений и места для предположений.


Теперь смотрим в Rust, где хоть и есть стандарт по именованию, но он не помогает читать чужой код.
Типы именуем CamelCase, функции и модули — snake_case.
Комбинированное внесение в область видимости заставляет спотыкаться:
use datafusion::logical_plan::{DFField, DFSChema, lit_timestamp_nano};


use std::path::{Path} дублирование разным регистром выглядит подозрительно избыточно, хоть и теоретически обоснованно, потому что модули и тип это разные сущности.


use std::sync::Arc — причем здесь потокобезопасная дуга? Нет, Atomically Reference Counted. Теперь мы подготовлены к std::sync::Condvar, std::path::PathBuf;
std::sync::mpsc::channel — Multi-producer, single-consumer FIFO queue communication primitive.
std::rc::Rc мы уже понимаем как Reference Counter; но std::rc::Weak вообще прилагательное. И мы додумываем, что это про слабый счетчик ссылок.


To/From, Into_ типы, трейты и вспомогательные функции тоже не самодостаточно именнованы, чтобы понимать о чем речь. Какое правило по превращение одного в другое: B = A.into() или B = B.from(A), и прочие противоречивости.


От аббревиатур в CamelCase к Rust сущностям.
std::boxed::Box, что по сути является указателем на динамически выделенную в куче память. И ничего общего с боксингом/анбоксингом значений в Java, или двумерной фигурой с 4 углами.


И std::borrow::Cow, который не про одолжить корову у соседа, и даже не про одолжение Copy-On-Write.
The type Cow is a smart pointer providing clone-on-write functionality: it can enclose and provide immutable access to borrowed data, and clone the data lazily when mutation or ownership is required.


std::cell::{RefCell, RefMut, Cell}; без подглядывания в референс мануал напрашиваются какие-то клетки или ячейки, а ведь это A mutable memory location. И это совсем не про memory buffer.

Может у меня глаз замылился, всё-таки пишу уже несколько лет на расте, но честно говоря не припоминаю, чтобы даже на заре знакомства с языком эти "неочевидности" вызывали вопрос. Хотя первое время "ломка" и была из-за того, что многое хотелось писать как привык в С++. Но да, люди разные, так что аргументировать личным опытом — такое себе. Теперь мне даже интересно у многих ли людей такие же вопросы возникают...


Комбинированное внесение в область видимости заставляет спотыкаться:

А мне наоборот нравится, что сразу видно где тип, где функция, где переменная, а где константа.


To/From, Into_ типы, трейты и вспомогательные функции тоже не самодостаточно именнованы, чтобы понимать о чем речь. Какое правило по превращение одного в другое: B = A.into() или B = B.from(A), и прочие противоречивости.

Вот это не очень понял. Почему From и Into именованы не самодостаточно, если всё что они делают — это просто преобразуют из одного типа в другой. Как-то даже затрудняюсь придумать как название можно расширить чтобы сделать "более очевидным".


Зачем два? Потому что много где не надо явно указывать целевой тип — он будет известен из контекста и достаточно просто а.into(), а там где нужно указать тип — там удобнее X::from. В доке, вроде как, вполне внятно это описывается, а так же какой из трейтов надо реализовывать чтобы автоматически получить реализацию второго.

From и Into зачастую реализуются для разных трейтов. И для сложных случаев, таких как datafusion библиотека, каждый тип может на себе иметь по десятку реализаций. Автоматический вывод всего конечно прекрасен, и C++ auto и type deduction не зря вводит и расширяет. Но в Rust иногда попадается f.into().into() что просто взрывает мозг. Но мои претензии все равно остаются сугубо субъективными и обусловленными С++ деформацией сознания. Был бы рад изучить популярным языком написанные статьи с предпосылками именования в Rust. Не верю, что придумав грамотные концепции и принципы были упущены правила именования. Скорее, я смотрю на них не стой стороны.

Но в Rust иногда попадается f.into().into()

Не может такого быть, компилятор в таком коде не может вывести типы.

Про virtual не понял. Вы хотите как в java, где все, что не помечено как final или private, по факту virtual? Или вообще vtable выпилить из языка? Если vtable убирать, то я против. Это очень мощная фича языка и я даже не представляю, как писать эффективный код без этого.

Посмотрите на примеры с гитхаба в том же пункте. Нет, мне не нужны везде virutal, наоборот, отделение типа и его полиморфизма

template<typename T>
struct Say {
  static void do_invoke(const T& self, std::ostream& out) {
    self.say(out);
  }
};
using any_animal = aa::any_with<Say>;
struct Cat {
  int field;
  
  void say(std::ostream& out) const {
   out << "Meow\n";
  }
};
struct Dog {
  std::string field;
  
  void say(std::ostream& out) const {
   out << "Woof!\n";
  }
};
int main() {
  any_animal Pet = Cat{};
}

Как вы можете видеть здесь any_animal - тип созданный библиотекой, он может хранить любой другой тип с требуемыми методами( в данном случае Say)

Вот только эта либа желает динамическую аллокацию памяти на КАЖДЫЙ чих. И да, таблицы виртуальных функций там всё ещё есть, только теперь девиртуализация с эти и чудом невозможна, любые оптимизации с вызовом final невозможны. Если для вас всё это ок, то может вам и плюсы не нужны? Ну пишите вы на питоне, зачем мучаться

Что вы несёте? Где там нужна аллокация на каждый чих? Может вы посмотрите как используют СЕЙЧАС виртуалньые функции, когда каждый объект выделен в куче?

А в библиотеке как раз этот недостаток убирается и можно как изменить аллокатор так и просто ничего не делая получать бонусы от small object optimization

Пожалуйста, если вы не разбираетесь в вопросе - не делайте таких уверенных утверждений

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

Правильно ли я понял, что автор библиотеки реализовал виртуальные методы на шаблонах, со своей собственной vtable? И что в итоге получили? Усложнение синтаксиса. Ну да ладно, привыкнуть можно. К генерации столь-же оптимального кода, как сейчас на встроенном vtable тоже есть вопросы. Я бы не стал тут полагаться на магию оптимизатора. А вот умеет ли библиотека (не сильно ковырялся в ней) делать множественное виртуальное наследование? Да, это спорная фича и вызывает у многих вопросы, но в моей практике множественное виртуальное наследование довольно часто оказывается самым удобным, быстрым и красивым решением задачи.

Так что все равно нет.

Однажды автор познакомится с Rust и будет ему счастье. Ну серьезно, для меня C++ это прежде всего ООП язык с упором на скорость и удобство разработки (в последних стандартах), мне нравится на нем программировать, но я делаю это без использования каких-либо сложных концепций (ламер на C++, использующий только умные указатели вне функций)

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

Забавно. Сейчас такие спеки есть разве что для WASM, то бишь к расту оно имеет посредственное отношение. Ну а на C ABI спеки как не было, так и нет, поэтому люди продолжают использовать, то, что исторически сложилось.

Надо себя заставлять. Через пару месяцев разработки на Расте вы к С++ не вернётесь. Просто надо научить мозг новым концепциям.

А можете кратко описать чем плох Rust или дать ссылки на что-нибудь почитать об этом? Было бы интересно, спасибо

Осталось уговорить ребят из C-- все это сделать

Следующий код полностью заменяет юнион, не имеет никакого оверхеда относительно него и имеет более понятный пользователю интерфейс (emplace / destroy)

Как вы лихо запихали placement new и reinterpret_cast в constexpr-контекcт. Можете продемонстрировать как вы замените вот такой код с помощью вашего union_t?

struct T {
    union {
        int n;
        float f;
    };
    constexpr T(int a) : n(a) {}
    constexpr T(float b) : f(b) {}  
};
int main() {
    constepxr T a = 1;
    constexpr T b = 0.5f;
    static_assert(a.n == 1);
    static_assert(b.f == 0.5f);
}

И реальный кейс: как вы планируете реализовать стандартный std::optional без union или std::variant?

Как же их заменить? Рекусивным(или через множественное наследование) туплом с элементами одного типа(да, это было очевидно)))

Вы же это несерьезно, правда?
https://cppinsights.io/s/62550a23 и вот это у вас будет в каждом юните трансляции?

И реальный кейс: как вы планируете реализовать стандартный std::optional без union или std::variant?

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

Хорошо. Можете, пожалуйста, на https://godbolt.org привести код инициализации и использования вашего union_t в constexpr-контексте. Не важно каким компилятором и стандартом вы это сделаете?

Как массив байт с правильным алигментом, прямо в статье код делающий всё что вам нужно

И было бы здорово посмотреть, думаю не только мне, как вы с помощью массива байт реализуется ваш fucking::optional<T> как literal type, что требует стандарт.

Для optional и variant вообще то исключения в стандарте на использование в constexpr контексте. Никаким языковым способом не реализуемо на данный момент сделать их constexpr. Только внутренняя реализация компилятора может это позволить

Если бы это было возможно, то был бы разрешён reinterpret_cast и bit_cast указателей в constexpr контексте, но это запрещено

Никаким языковым способом не реализуемо на данный момент сделать их constexpr.

Это реализуется за счет union.

Заменить это всё можно одним фундаментальным типом byte и указателями, действительно: с помощью byte и системы типов С++ можно создавать любые типы, в том числе аналогичные int, double, float, bool и т.д. из фундаментального набора. Тут мы убиваем сразу несколько зайцев - нет больше исключений для фундаментальных типов в разрешении перегрузки, нет исключений в шаблонном коде для наследования( от фундаментальных типов нельзя наследоваться) ну и другие более мелкие исключения для подобных типов уходят в прошлое

И одним из этих убитых "зайцев" будет видимо constexpr.

нет не реализуемо, просто создать переменную в юнионе - бесполезно. Пользоваться юнионом на компиляции вы не сможете (так как на компиляции undefined behavior это ill formed программа)

Это равносильно созданию одной переменной, о чём я и написал. При реализации variant вы не сможете обойтись без placement new и reinterpret_cast в том или ином виде.

Вы всерьёз смотрите на то, что выплёвывает cppinsights? Это не то что реально происходит, а лишь какая то демонстрация непонятная

https://godbolt.org/z/oW4zEc7hK

И во вторых, если в ЯЗЫКЕ нет массива, это не значит что РЕАЛИЗАЦИЯ(например от msvc) не сможет его сделать на built-in'ах, при этом ничего не потеряв вы убрали лишнюю вещь из языка. Перечитайте и подумайте над этим

если в ЯЗЫКЕ нет массива, это не значит что РЕАЛИЗАЦИЯ(например от msvc) не сможет его сделать на built-in'ах

То есть Вы предлагаете рассчитывать на нестандартные вендорские расширения (в ещё большей степени, чем это происходит сейчас)?

Вот вам очень похожий случай:

template <class _Ty, _Ty _Size>
using make_integer_sequence = __make_integer_seq<integer_sequence, _Ty, _Size>;

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

И в языке нет лишних сущностей и вы ничего не потеряли как пользователь

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

Если другой компилятор не даёт вам этого, значит он не поддерживает стандарт языка, значит он не компилятор С++, значит вам не нужно переносить на него код

Так если это требуется стандартом - значит, это всё-таки есть на уровне языка?

на уровне стандартной библиотеки, а не core

UFO just landed and posted this here

так вы используйте не интринсики, а интерфейс стандартной библиотеки. Я указал, что это реализуемо рекурсией, но никто так не делает

Это не то что реально происходит, а лишь какая то демонстрация непонятная

Хорошо, не нравиться cppinsight, давайте посмотрим на AST или трансляция кода это тоже не то, что реально происходит?

Вот для вашего примера
https://godbolt.org/z/3f1rMWG4e

А вот для обычного массива
https://godbolt.org/z/4T6hGoKnW

И во вторых, если в ЯЗЫКЕ нет массива, это не значит что РЕАЛИЗАЦИЯ(например от msvc) не сможет его сделать на built-in'ах, при этом ничего не потеряв вы убрали лишнюю вещь из языка.

Это также не значит что реализация СМОЖЕТ это сделать.

Но если предположить что смогла. Вот я могу взять clang и использовать стандартную библиотеку gcc или наоборот. По libc++, например, https://libcxx.llvm.org/#platform-and-compiler-support. Как это должно работать? Вы предлагает стандартизировать built-in'ы?

У компиляторов есть ограничение на шаблонные рекурсии, так что ваш аналог std::array работает только на крошечных примерах (для clang, если память не изменяет, до 512 ограничение)

На счет слова final - оно позволяет оптимизировать прямые вызовы функций final-типа, т.е. теряется оверхед на поход в vtable

если в ЯЗЫКЕ нет массива, это не значит что РЕАЛИЗАЦИЯ(например от msvc) не сможет его сделать на built-in'ах, при этом ничего не потеряв вы убрали лишнюю вещь из языка. Перечитайте и подумайте над этим

Практика показывает, что ничего не оптимизируется. Или оптимизируется абсолютно также как без final

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

Ну и да, вообще-то array в статье реализован без рекурсии

Сложилось впечатление, что автору статьи С/С++ не подходит. Лучше ему поискать другой язык.

Нет же. Автор исходит из мысли «мне это не нравится (я это не использую), значит всем остальным не надо».

В одних только комментариях к этой статье примеров от пользователей union куча, ВСЕ из них с undefined behavior. Кажется это о чём-то говорит

Уважаемый автор предлагает нечто очень странное, на мой взгляд.

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

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

Если мне не изменяет склероз, то вот тут:

template<one_of<Ts...> U>
constexpr U& emplace(auto&&... args) {
  return std::launder(new(data) U{std::forward<decltype(args)>(args)....});
}

std::launder не нужен, т.к. у нас есть результат placement new, который "отмывать" уже не нужно. std::launder потребовался бы вот в таком случае:

template<one_of<Ts...> U>
constexpr U& emplace(auto&&... args) {
  new(data) U{std::forward<decltype(args)>(args)....};
  ...
  return std::launder(reinterpret_cast<U*>(data));
}

PS. Статью целиком не осилил.

Короче, выбросить всё, что активно используется в embedded. Не, так не пойдет.

Я категорически против большинства ваших предложений:)

Вы предлагаете выбросить все простое и заменить это какими-то мозгодробительными решенями на шаблонах.

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

Ваши примеры при беглом взгляде совершенно непонятны. Наверное, если помедитировать над ними, то станет понятнее, но зачем? union, массивы, базовые типы - это основы, они просты и понятны. А вы предлагаете и дальше продолжить переносить кишки компилятора наружу, в шаблоны. И в результате С++ превратится в интерпретатор некоего функционального языка с уродским синтаксисом, на котором будет написан некий объектно-ориентинованный язык (с не менее уродским синтаксисом), и вот на смеси этих двух языков и предлагается писать программы обычным программистам.

Отчасти соглашусь с отказом от сишного приведения типа - оно неудобно для поиска в коде, т.к. нет ключевого слова. Также соглашусь с тем что typedef и using пересекаются, можно оставить что-то одно.

UFO just landed and posted this here

??? В чём проблема?) Я специально использовал здесь не [], чтобы самые умные читатели не начали причитать, что я тут синтаксис массива использую в реализации массива.

К оператору [] претензий нет, а вот к массиву в языке - есть. Как показано в статье, массив как языковая абстракция не нужен

К оператору [] претензий нет, а вот к массиву в языке - есть

А надо бы наоборот. Потому что сейчас это может работать так:

foo = 5[bar];

Про union вообще не понял. Очень частая задача в с++ (я пишу под микроконтроллеры) это представить какую либо структуру как массив байт( например что бы сохранить на флеш, или отппавить кудато) Бывает так же , например, ситуация когда dword хочется представить как два int ну итд. Без подобных преобразований просто не обойтись. Как автор предлагает действовать без union?

UFO just landed and posted this here

у меня один вопрос, вот здесь:

template<one_of<Ts...> U>
constexpr void destroy_as() const {
reintepret_cast<const U*>(reinterpret_cast<void*>(data))->~U();
}

мне предлагается руками писать тип, который я хочу дестроить? Или я просто чего-то не понял?

В обычном union вам никто даже не предлагает этого, просто если вы не сделаете - ваша программа не является программой на С++

это ответ не на совсем тот вопрос, но я понял, спасибо.

Если union не нужен, то почему почти в каждом игровом движке, даже на C++17, они пишут самодельную реализацию Variant типа, под капотом которого union, а не шаблон c new? Или интерпретаторы с универсальными типами?

Как человек, который реализовывал variant и многие другие вещи могу точно сказать, что реализовать variant через union СЛОЖНЕЕ, чем просто через массив байт. ОСООБЕННО таким образом, чтобы соблюсти гарантии языка

А в чём именно сложность-то? Тут выглядит довольно просто.

Ваше предложение тоже не C++ и что?

UFO just landed and posted this here
Реализация массива без массива:

Арифметика указателей не работает на void*. Я бы понял, если бы каст был к char* или byte*, но так, как сейчас, это на конформном компиляторе не работает.


6 — typedef

typedef можно положить в заголовочный файл, разделяемый между C и C++, using — нет.

Там каст не к void*, а через void* к другому типу

Что-то автор не сильно углубился в тему: всё равно всё превращается в биты, давайте писать сразу ими.

Да. Из типов данных оставить только некий bit. Оптимизирующий компилятор должен «узнавать» реализации популярных операций над группами битов, и использовать имеющиеся функции процессора.

Автор, пожалуйста, сделайте что-нибудь с инициализацией! Сейчас инициализировать можно знаком =, круглыми скобками, фигурными скобками…

И с преобразованиями типов тоже что-нибудь сделайте. Сейчас можно писать int(a), (int)a, static_cast<int>(a).

правда можем убрать из С++ массивы не потеряв ничего

И буквально абзацем выше через них юнионы заимплеменчены. Что ж вы сразу не попробовали хотя бы примеры без всех ненужных фич написать? Может, потому что так получается ещё непонятнее?

Как дела с арифметикой указателей у массивов-через-иерархию наследования? Даёт ли стандарт гарантии относительно раскладки в памяти полей классов, которые не POD? В целом эти вот массивы через reinterpret_cast выглядят как прямая дорога в UB.

Предлагаю расширить идею автора: берём ассемблер с одной инструкцией db 0xHH, подкладываем его под Forth, и в библиотеке имплементим все эти вот ваши новомодные структуры, юнионы и шаблоны, пока не получим С++. Всё по стандарту (Forth'а), в языке никаких ненужных фичей нет, всё вообще эксельсиор и дистинктивно.

То есть std::array<std::byte, N> это вам непонятно? Как реализовать этот массив я показал

1) Как только вы в своём велосипедном union задействовали placement new, - вы автоматически выбросили constexpr. В отличие от оригинального, встроенного в язык.

2) У любого компилятора есть ограничения на размер набора параметров шаблона и на глубину рекурсии времени компиляции. Сделайте массив на 100000 элементов.

3) std::monostate.

4) Докопались до столба. Если вы реально занимаетесь поддержкой зоопарка платформ, то разрядность int вас будет волновать лишь наряду с другими вещами. А если не занимаетесь, и не прижимаетесь к границам максимальных значений на вашей платформе, то повсеместно уточнять int8_t, int16_t, int32_t, int64_t для каждой переменной - только голову себе морочить.

4.5) Да. Коропоративные стили кода и агрессивные линтеры вам в помощь.

5) Большей частью - да. Но компиляторы умеют ловить ошибки в сишных вариадиках. А по части производительности - printf, например, жэстачайше уделывает std::iostream. И где нужна скорость, нужно оставить возможность.

6) Да. Только для хедеров, совместимых с обоими языками, придётся сохранить. Потому что сишный ABI рулит как наибольший общий знаменатель.

7) Макросы нужны. Не нравятся такие - предложите другие. m4? Гигиенические макросы из лиспа или немерле?

8) Действительно, зачем нам эти операторы, если сами же в первом же пункте на них что-то наколбасили?

9) Много не потеряем, можно всё лепить на struct - и прописывать доступы.

10) final - чтобы рукожопые студенты не наследовались от std::vector. Типы с семантикой значения должны быть final, попробуйте понять, почему.

11) Полиморфизм без таблиц - это или оверхед в экземпляре класса, или всё равно таблицы, только без ключевого слова virtual. А вообще, стирание типов давно уже есть в boost::any. Да и даже в std::shared_ptr.

12) Гвидо ван Россум, отстаньте уже, пожалуйста. Над питоном в младенчестве надругались, это не значит, что надо над всеми остальными языками то же самое делать.

5) Большей частью — да. Но компиляторы умеют ловить ошибки в сишных вариадиках. А по части производительности — printf, например, жэстачайше уделывает std::iostream.

А fmtlib уделывает printf.


Но компиляторы умеют ловить ошибки в сишных вариадиках.

А можете продемонстрировать пример? И желательно не на стандартной функции, для которой поддержку в компиляторе можно и захардкодить.

Вот объявление функции в текущем проекте.

__attribute__((format(printf, 6, 7)))
void Panic(
  const char* file,
  long line,
  const char* function,
  const char* expr,
  bool abort,
  const char* format, ...) noexcept;

Атрибут format поддерживается и гусём, и шлангом.

  1. Вы не знаете С++, constexpr создание элемента где-то возможно только с С++20 std::construct_at, для которого сделано исключение на конструирование на компиляции

  2. В реализации массива в статье нет рекурсии, вы не знаете С++ и не понимаете этого

  3. Нет, это ничего не имеет общего с std::monostate. это называется тег тип и таких типов в стандартной библиотеке великое множество, std::piesewise_construct, std::nothrow, std::monostate, std::forward_iterator_tag и все остальные теги и так далее

  4. Если вы не уточняете какое максимальное число вам нужно хранить в инте, то вы либо используете гарантированные стандартом 2^16, либо ваш код потенциально содержит undefiend behavior

  5. printf ужасно медленный, он уделывает iostream потому что iostream сделан плохо. В древние времена туда добавили виртуальные функции, виртуальное наследование, возможность ребиндить стрим на другой, кривое форматирование и громадное количество флажков ошибок/исключений/форматирования

    В современном С++ это всё может быть заменено куда более хорошими вещами, например std::print , std::format и так далее. Они быстрее printf, потому что в них нет рантайм определения типа аргумента, это просто факт

    Компиляторы делают костыли, чтобы проверять форматные строки printf на компиляции, стандарт С++ ничего об этом не говорит

  6. extern "C"

  7. Функциональные макросы не нужны, они ужасны

  8. В этом же пункте я ЯВНО указал, что нужен ТОЛЬКО оператор РАЗМЕЩАЮЩЕГО new, который и использован в 1 примере. Размещающий(Placement), тот который НЕ выделяет память и НЕ связан с системным аллокатором

  9. Да, именно поэтому class лишнее в языке

  10. std::vector не объявлен final, потому что разработчики стандарта понимают, что final даже в случае std::vector неприменим

  11. В статье нет ничего про убирание таблиц, есть про убирание ключевого слова virtual и связанных с ним нагромождений в стандарте. boost::any и std::any ужасны и неприменимы на практике, а в библиотеке по ссылке https://github.com/kelbon/AnyAny концепции стирания типов обобщены и легко кастомизируемы, по сути этот репозиторий - доказательство, что virutal лишнее в языке

  12. Скажите это коммитету по стандартизации ,который принял deducing this в С++23 и это теперь реальность плюсов

  1. Это вы не знаете С++. В реализации массива нет рекурсии, зато есть стопятьсот параметров (распакованных из make_index_sequence) и стопятьсот баз - разных типов (хотя и несущих однотипное поле).

template<typename T, size_t... Is>
struct array_impl<T, std::index_sequence<Is...>> : array_value<T, Is>...{}

Напоминаю, что size_t - может быть хоть 32-, хоть 64-битным.
Удивите компилятор, подсуньте ему класс с несколькими миллиардами параметров.

Ничего хуже ошибки компиляции не получу. А вот удивление от stack overflow вам гарантированно с подобным по размеру массивом

Ничего хуже и ничего лучше вы не получите.
А статические массивы размером в несколько миллионов элементов - вообще не проблема.
Да и на стеке тоже.

Что вообще за упоротость такая? Массивы ему не нравятся, а родить структуру с крендельоном баз и потом реинтерпретировать её, как указатель!!! на массив!!! - это труъ?

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

И вы будете тут спорить, кто знает язык, а кто не знает?
Адресную арифметику используете - значит, это указатель на массив.

reinterpret_cast<T*>(reinterpret_cast<void*>(this)) + n

вот это вот +n для указателей на одиночные элементы бессмысленно и UB

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

Удивительно, но если из языка УДАЛИТЬ массивы, то никаких указателей на массивы не будет

А зачем в языке без массивов операции адресной арифметики?

чтобы реинтерпретировать адрес и если там динамически лежит тот тип, который реинтерпретирован работать с ним. Как собственно сейчас и сделано, только юзлес концепция массивов зачем то есть

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

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

если нет определения массива, то ничто не массив по определению.

Отсутствия массива в языке не отменяет его возможность как абстракции на уровне библиотеки, точно также как отсутствие динамического массива в языке не отменяет существование std::vector

если нет определения массива, то ничто не массив по определению

Т.е. можно просто убрать определение и упоминания массива из стандарта, и ваш пункт можно вычеркивать из статьи? Это ведь не сложно, оператор индексирования a[b] назовем оператором суммы целого и указателя, а синтаксис объявления массива станет синтаксисом объявления области памяти длиной в произведение размера типа и целого положительного.

Отсутствия массива в языке не отменяет его возможность как абстракции

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

Нужно убрать не только определение, но и сопутствующие мусорные пункты стандарта и синтаксис(создание массива например)

a[b] это не вызов оператора индексирования, а вызов оператора[] с аргументами a и b, семантику индексирования это вы ему придумали.

Перегрузка позволяет там и функции вызывать просто вместо круглых скобок квадратные делать

Не связаны никак адресная арифметика(прибавление числа к числу) и массивы

но и сопутствующие мусорные пункты стандарта и синтаксис

И адресную арифметику.

a[b] это не вызов оператора индексирования

Конкретно для массива это оператор индексирования.

Не связаны никак адресная арифметика(прибавление числа к числу) и массивы

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

... назовём оператором суммы - и получим старый злобрый K&R C.
В котором k[arr] имеет такой же смысл, как и arr[k], хотя для прикладного, не извращённого, программиста это выглядит дичью.
(Да, в С++ это тоже имеет тот же смысл для голых указателей - из соображений совместимости с Си)
(И если упороться, то можно и для некоторых пользовательских типов определить этот оператор вывернутым способом)

size_t гарантированно вмещает в себя указатель, соответственно иметь массив размера больше чем max size_t - бессмысленно абсолютно, т.к. он буквально по определению из стандарта вмещает в себя любое количество элементов на платформе

Статических массивов в миллиарды или миллионы и даже тысячи элементов на стеке не бывает, а если бывает вы пишите ужасный код, не пишите так больше

  1. extern "C" - не про переключение языка, а про правила декорирования имён. Каким образом это позволит вам избавиться от typedef в пользу using? А вот писать код на общем подмножестве Си и С++ - это вполне посильная задача. Но если вы выкинете typedef, то она станет непосильной.

  1. Макросы препроцессора CPP ужасны. Но макросы вообще - нужны. Ломаете что-то - предлагайте своё. Встраивать в язык макросы, работающие напрямую с AST - ну, может быть, к С++26 кто-нибудь сможет родить непротиворечивый пропозал. Встраивать в процесс сборки сторонние препроцессоры - сомнительное развлечение. Но можете попробовать. Какие препроцессоры порекомендуете, и чтоб они ещё и не конфликтовали с синтаксисом С++?

я сказал убрать функциональные макросы, а не все. Функциональные это принимающие аргументы

А нужны именно функциональные. Просто дефайны - это просто константы такие лексические.
Много вы напрограммируете на константах, если у вас функций нет?

Не нравится синтаксис и возможности CPP, предложите другой макроязык.
template haskell видели? Немерле видели? Лисп, наконец? Это примеры того, как макроязык встроен.

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

#define function(arg) function_(__FILE__, __LINE__, #arg, arg)

  1. std::source_location

  2. нахер мне эта "замечательная" возможность? Уж обошёлся бы как нибудь

  1. Классы стандартной библиотеки не финализированы отчасти потому, что это разломает совместимость с кодом, в котором наследование уже сделано.

Слышали что-нибудь про срезку? Как предлагаете бороться с ней?
Наследование от вектора - это грабельки. Вы тут ратуете за обезграбливание языка (си-стайл-каст убрать, например), и тут же предлагаете отказаться от обезграбливающей фичи.

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

Ага-ага. Нет наследования - нет проблемы.
Интерфейсы и реализации, в стиле вижуал бейсика (который не дотнет). Это не дерьмо.
А принцип подстановки Лисков - дерьмо.

  1. Как proof of concept - покатит, а на практике...

Количество писанины - выросло. Читаемость - упала.
Виртуальные вызовы с таблицами - никуда не делись. (Только сформированы руками).
Вместо семантики суперкласс-субкласс получили семантику интерфейс-реализация.
Да, ООП в смысли Гради Буча и Барбары Лисков - та ещё сомнительная штука, но блин, это уже классика.

Если вы свою библиотеку применяете на практике, то какие выгоды, кроме эстетического удовлетворения, получаете от неё?
Мне вот даже эстетики тут не видно. Вместо одного короткого слова virtual - гора какого-то бойлерплейта со служебными классами-трейтами и функциями-вызывалками.

И да, boost::any - как и многое в бусте - это ужас. Потому что это тоже такие вот proof of concept'ы, неожиданно заехавшие в продакшен.
Но не всё из буста переезжает в стандарт, а только лучшее.
Оказалось, что старые добрые виртуальные функции - лучше и концепции, и референсной реализации.

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

Читаемость не упала, просто вы привыкли сидеть копаться в 4 этажном наследовании.

std::any - ужасен, потому что не кастомизируем. Библиотека это исправляет

Зато я не привык копаться в 4-этажном копипазме служебных функций и служебных трейтсов на каждый чих.

  1. Именно monostate. Абсолютно та же семантика. И даже название то же самое. "тип с единственным ничего не значащим значением"

std::monostate имеет конкретную цель - дефолтно конструируемый тип, умеющий сравниваться, позволяющий быть дефолтно конструируемым std::variant в шаблонном коде

Если для вас все типы
struct time {
int t;
};
struct iq {
int i;
};

Одинаковые, то вам явно следует изучить теорию типов хотя бы на уровне интов

UFO just landed and posted this here

"Не хочу std::monostate, хочу kelbon::aa::monostate_type"

нет, чтобы создать golang нужно из С++ ещё удалить все шаблоны и всё хорошее

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

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

Чёт лёгенько вы обошлись с сипипи, я вот в своей статье ваще бомбил от точек с запятой в языке. Вот так будете язык урезать и изобретёте питон в итоге

Articles

Change theme settings