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

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

1 - union - сумм тип из 70х, в С идея хранения одного типа из нескольких в одном участке памяти выглядит неплохо и сейчас, ведь там все типы это набор байт с заданным размером.

В С++ же использование union это автоматическое undefined behavior, например:

#include <string>
union A { int x; float y;};
union B {
  B() {} // требуется написать какой то конструктор и деструктор
  // но обратите внимание, что написать деструктор правильно невозможно
  // (попробуйте, если не верите)
  ~B() {}
  std::string s;
  int x;
};
int main() {
  A value;
  value.x = 5;
  value.y; // undefined behavior, обращение к неактивному члену union
  B value2;
  value2.s = "hello world";
  // undefined behavior, поле s неактивно и используется 
  // (в операторе= для std::string)
}

Как вы видите использовать union без ошибок просто невозможно, при этом вам постоянно придётся вручную вызывать правильный деструктор для объекта и вместо приравнивания делать placement new в нужное поле. Так зачем же так мучаться, если можно сделать нормальный тип с хорошим интерфейсом БЕЗ какого либо оверхеда относительно юниона?

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

Смотреть только если знаете С++
template<typename ... Ts>
struct union_t {
  alignas(std::ranges::max({alignof(Ts)...})
  std::byte data[std::ranges::max({sizeof(Ts)...});
  
  template<one_of<Ts...> U>
  constexpr U& emplace(auto&&... args) {
    return std::launder(new(data) U{std::forward<decltype(args)>(args)....});
  }
  template<one_of<Ts...> U>
  constexpr void destroy_as() const {
    reintepret_cast<const U*>(reinterpret_cast<void*>(data))->~U();
  }
};

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

2 - массивы

Это может звучать странно, но мы правда можем убрать из С++ массивы не потеряв ничего (убрав этот чудовищный синтаксис char(&&...arr)[N] (угадайте в комментариях что это значит) )

К тому же массивы почему то не копируемы и не умеют в мув семантику, что делает их самыми неполноценными типами во всём языке

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

Интересный факт:

в тексте стандарта С++ есть исключение аж в цикле for для сишных массивов... Что подтверждает очевидное - массивы безумно плохо соотносятся с остальным языком

Реализация массива без массива
template<typename T, size_t I>
struct array_value { T value; };
template<typename, typename>
struct array_impl;
template<typename T, size_t... Is>
struct array_impl<T, std::index_sequence<Is...>> : array_value<T, Is>...{};

template<typename T, size_t N>
struct array_ : array_impl<T, std::make_index_sequence<N>> {
  // тут какой-то интерфейс массива по вашему желанию
    T& operator[](size_t n) {
        // такая реализация для краткости
        return *(reinterpret_cast<T*>(reinterpret_cast<void*>(this)) + n);
    }
};

3 - тип void

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

struct [[maybe_unused]] nulltype {};
 // Вот и всё... Да и аттрибут [[maybe_unused]] тут разве что для красоты

4 - все фундаментальные типы...
Кажется мы идём по нарастающей, на что же автор статьи тут замахнулся? На int?!

Да, не удались в С фундаментальные типы, а С++ их унаследовал. Кто в здравом уме будет использовать int, который может занимать 8 байт, но гарантирует свои значения только до 2 ^ 16??? Это буквально создатель ошибок(особенно у новичков)

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

4.5 - приведения типов из С - это.просто.не.должно.компилироваться. (но оно компилируется) https://godbolt.org/z/fz6eMEeqG

int main() {
    (void)(5), (void)5, void(5);
}

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

И даже не смотря на то, что так реализован знаменитый printf(const char* pattern, ... ) <- кто не понял, многоточие это рантайм аргументы! Любые! Это выглядит самый большой костыль в истории программирования, а как этим пользоваться... Ух... макросы __VA_START__ __VA_COPY__ и громадная куча ещё всего связанного с этим будут сниться в кошмарах сишникам десятилетиями, а С++ пожалуй должен просто удалить этого демона из языка и забыть(и добавить за счёт удаления этого новые возможности пакам шаблонных аргументов)

6 - typedef - ну тут всё просто, в С++ есть отличная замена этому слову, просто сравните:

typedef void(*foo)(int); // foo теперь алиас на void(*)(int) (указатель на функцию)
// то же самое, но на С++
using foo = void(*)(int);

Смысла оставлять typedef в языке нет...))

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

Вот, кажется на этом этапе мы вычистили почти весь С из С++ и почти получили чистые ++(плюсы). Пора рассмотреть что стоит удалить тут!

8 - операторы new и delete

Действительно, зачем нужны в языке эти операторы, если всё давно перенесено на уровень абстракций аллокаторов, а память на низком уровне можно продолжать выделять через malloc?! Как вообще можно было догадаться внести систему(системный аллокатор памяти) на уровень языка?

Вы только посмотрите на даже не правила, а просто список перегрузок одного только new

СТРАШНО

Нужно только оставить размещающую версию оператора new для вызова конструктора по нужному адресу, всё остальное, особенно перегрузки new / delete использовать в современном С++ просто запрещено, если вы не хотите чтобы вас засмеяли

9 - ключевое слово class - ну тут я просто оставлю ссылку на мою же статью про бесполезность этого ключевого слова https://habr.com/ru/post/662351/

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

11 - виртуальные методы :

Вызывают громадную кучу ошибок

Неэффективны, стимулируют писать архитектурно плохие решения, неэффективно использовать память, не позволяют использовать весь остальной язык, если используется ключевое слово virtual, и САМОЕ ГЛАВНОЕ - могут быть полностью заменены на другие языковые возможности без потери функционала(и с приобретением производительности, удобства, повторяемости кода, проверок на компиляции и т.д....)
Реализация динамического полиморфизма без виртуальных функций и их проблем: https://github.com/kelbon/AnyAny

12 - методы ( указатель на текущий объект внутри реализации типа )

В С++23 появляется(наконец) deducing this, благодаря которому можно будет явно декларировать передачу this в методы типа, при этом такой "метод" будет фактически функцией(с точки зрения языка), а значит в последующем(вместе с удалением виртуальных методов) можно будет избавиться от самого понятия МЕТОД в языке С++(и указателя на эту вещь) (не дай боже вам перед сном увидеть декларацию указателя на метод)

struct A {
void foo(this A& self);
};

При этом возможно, что постепенно и ключевое слово this потеряет прежнее значение и останется только такое - декларация явной передачи ссылки/значения типа в функцию

Ну вот и всё, помечтали о великолепном hole C++, можете теперь пойти и опять продолжить писать хрень с виртуальным наследованием, забытым виртуальным деструктором на полиморфном типе и сишными кастами, удачи...

Комментарии (307)


  1. panteleymonov
    19.06.2022 20:37
    +9

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

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

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


    1. Kelbon Автор
      19.06.2022 20:42

      С развивается?..


      1. Artem_Mironov
        19.06.2022 21:38
        +14

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


        1. Kelbon Автор
          19.06.2022 21:43
          +2

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


          1. Artem_Mironov
            20.06.2022 02:12
            +2

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

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

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

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


          1. MarkusD
            20.06.2022 10:03
            +5

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

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

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

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


  1. Kotofay
    19.06.2022 20:39
    -1

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

    Это Java(+Graalvm).

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

    Hidden text

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


    1. Kelbon Автор
      19.06.2022 20:41
      +19

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


      1. sandersru
        19.06.2022 21:21
        +32

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

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

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


        1. hobogene
          20.06.2022 00:14
          -3

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


        1. 0xd34df00d
          20.06.2022 01:04
          +5

          чем плох ооп?

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


          Какие альтернативы?

          ФП и нормальные модульные системы.


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


          1. vanxant
            20.06.2022 01:13
            +29

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

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


            1. 0xd34df00d
              20.06.2022 01:43
              +8

              Задач, которые хорошо ложатся на ФП, в реальном мире ещё меньше.

              Классическое ФП — это просто про композицию функций. Функции композировать заведомо легче, чем объекты.
              Современное ФП — это ещё и про контроль эффектов в типах. Типы существенно проще и скейлятся лучше, чем контролировать эффекты в названиях паттернов и соглашениях команд.


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

              Вопрос привычек.


              Да и очень много людей понимают ООП, что ли?


              А лидируют, внезапно, javascript и python, где большинство кандидатов пишут в старом-добром императивном стиле "пойди туда, возьми то", без всяких извратов с ООП и ФП...

              Так мы ж обсуждали ООП vs ФП, а не smth vs «нафигачить по-быстрому».


              1. nin-jin
                20.06.2022 08:56
                +5

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

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


                1. 0xd34df00d
                  20.06.2022 09:04
                  +6

                  Объекты прекрасно компонуются.

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


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

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


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

                  Эффекты как раз очень существенны, я не хочу их скрывать. Скрывать я хочу детали реализации, и пусть они как раз остаются внутри функций.


                  1. nin-jin
                    20.06.2022 09:51
                    +8

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

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

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


                    1. 0xd34df00d
                      20.06.2022 17:29
                      +1

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

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


                      Монады и прочие моноиды — это просто интерфейсы с некоторыми правилами о том, что должны делать методы. У вас есть книжки по [гуглю "most common java interfaces"] по Appendable, Callable и прочим ООП паттернам? Кстати, Appendable выглядит подозрительно близко ко всяким моноидам. Или там flatMap какой-нибудь — о, у вас в жс уже походу монады завезли, когда книжки ждать?


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

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


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

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


                      1. nin-jin
                        20.06.2022 17:37
                        -4

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

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

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


                      1. 0xd34df00d
                        20.06.2022 17:51
                        +3

                        Что такое "уровень GoF"?

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


                        Вы не поверите, но ООП паттерны — это тоже не более чем интерфейсы с правилами.

                        Что такое моноид? Моноид — это любой тип X и функции mappend : X → X → X и mempty : X такие, что


                        • Ассоциативность: mappend a (mappend b c) = mappend (mappend a b) c
                        • Единица слева: mappend a mempty = a
                        • Единица справа: mappend mempty a = a

                        Всё.


                        Что такое монада? Монада — это любой конструктор типов M и пара функций >>= : M a → (a → M b) → M b и pure : a → M a, для которых выполняется


                        • (m >>= f) >>= g = m >>= (\x -> f x >>= g) — блин, опять ассоциативность.
                        • pure v >>= f = f x — опять единица.
                        • m >>= pure = m — и снова единица.

                        Всё.


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


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

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


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

                        И эффекты, которые они выполняют, будут сделаны несколько раз. Письмо клиенту уйдёт несколько раз, деньги будут списаны несколько раз, и так далее.


                      1. Helltraitor
                        20.06.2022 19:14
                        +6

                        Куча непонятных символов. Можно было хотя бы словестно написать


                      1. 0xd34df00d
                        20.06.2022 20:18

                        Для развития педагогических навыков — какие символы тут непонятны?


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


                      1. DarkEld3r
                        21.06.2022 10:09
                        +1

                        Для развития педагогических навыков — какие символы тут непонятны?

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


                      1. 0xd34df00d
                        21.06.2022 19:27

                        Я бы ожидал, что средний сишник ходил в школу, а записи вроде f : ℕ → ℝ там встречаются.


                      1. vanxant
                        21.06.2022 20:47
                        +1

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


                      1. 0xd34df00d
                        21.06.2022 21:04

                        В логике предикатов это тоже можно прочитать, будет вполне нормально — спасибо изоморфизму Карри-Говарда.


                      1. F0iL
                        21.06.2022 23:49
                        +4

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

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

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

                        Более того, на прошлой работе ради интереса запуливал в чатик разные интересности и задачки, и случайно выяснилось, что для подавляющего большинства разработчиков запись вида

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


                      1. Zenitchik
                        22.06.2022 13:56
                        -1

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

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


                      1. eao197
                        20.06.2022 20:03
                        +4

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

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

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


                      1. 0xd34df00d
                        20.06.2022 20:21
                        +3

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

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


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

                        При этом даже изложение в GoF слишком слооожна, поэтому вышли ещё всякие head-first design patterns. И, к слову, группа обсуждения паттернов в одной компании, где я работал (и где были достаточно прошаренные плюсисты, датасайентисты и прочие), избегала GoF (потому что слооожна, да), и вместо этого читала-обсуждала по паттерну из этих head-first раз в две недели.


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


                      1. eao197
                        21.06.2022 08:07

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

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

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

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


                      1. 0xd34df00d
                        21.06.2022 19:46

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

                        ХЗ, для меня вообще ничего специфичного нет ни в них, ни в парсерах, ни в чём. Но у народа возникают сложности.


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

                        Я вообще изначально написал, что ООП-паттерны не то чтобы очевидны среднему программисту.


                      1. eao197
                        21.06.2022 19:53

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

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


                      1. rblaze
                        21.06.2022 18:21

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


                      1. nin-jin
                        21.06.2022 14:39

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


                      1. 0xd34df00d
                        21.06.2022 19:29
                        +1

                        Вот смотрите, я потратил минут 5-10 своего времени, чтобы написать конкретный комментарий с конкретными примерами правил. Если вы говорите, что паттерны ООП — это тоже интерфейсы с правилами, как моноиды всякие, то не затруднит ли вас потратить ваши 5-10 минут на описание правил для этих паттернов?


                      1. ReadOnlySadUser
                        21.06.2022 15:46
                        -1

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

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

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

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

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


                      1. 0xd34df00d
                        21.06.2022 19:45

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

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


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


                        Вот монады, хоть тыщу раз имеют эти правила, я так и не понял зачем нужны.

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


                        В то время как фабричный метод я объясню ребенку за 10 минут.

                        Ну попробуйте :]


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

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


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


                      1. ReadOnlySadUser
                        21.06.2022 20:05
                        +1

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

                        А зачем?

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

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

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

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

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

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

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

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


                      1. 0xd34df00d
                        21.06.2022 21:24
                        +5

                        А зачем?

                        Чтобы понять, когда использовать одно, а когда — другое. Это ведь не зря разные паттерны?


                        И чтобы более эффективно читать код, где один класс называется FooBridge, а другой — BarAdapter.


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

                        А можете с тем же успехом ответить про фабричный метод? Ну, чтобы «Ответ: во многих ситуациях, в основном когда надо что-то…» что?


                        Описание правильной применимости которых точно также займёт по целой книге.

                        Откуда там на целую книгу описаний? Почти всё там вообще из типов и названий функций понятно.


                        Нужно, чтобы одно монадическое выполнялось только тогда, когда другое даст True? Используете whenM. Нужно написать что-то вроде if/then/else, но с монадическим условием? Используете ifM.


                        Забыли, что есть whenM, но понимаете, что вам вот в данном контексте нужна функция с типом m Bool -> m () -> m ()? Открываете хугл, пишете этот тип, получаете, выбираете между whenM и unlessM по значению слова «when» и «unless», даже документацию читать не нужно.


                        Знаете, что в стандартной библиотеке есть функция mapMaybe с типом (a -> Maybe b) -> [a] -> [b] и достаточно очевидной семантикой, но вам нужна версия, которая бы поддерживала Maybe, завёрнутую в монаду? Снова открываете хугл, пишете, находите достаточно ожидаемый результат.


                        Не знаю, о чём тут книги писать.


                        Не потому, что они не нужны, а просто потому, что ФП как таковое никто не использует.

                        Хаскелисты не пользуются всеми этими вещами, по-вашему? Все эти пакеты существуют и, более того, добавляются в обратные зависимости для красоты?


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

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


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


                        Или с тем же успехом можно взять, скажем, джаву, попытаться объяснять её через объяснение системы типов, состоящей из System Fω c сабтайпингом и ограниченным полиморфизмом, прочитать все N сотен страниц Пирса по теории типов, нихрена не понять и сделать вывод, что в Java без PhD в матлогике и теории типов делать нечего. Разумный ли это вывод? Как по мне — нет.


                      1. svr_91
                        21.06.2022 08:29

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

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


                  1. nikolas78
                    20.06.2022 17:05

                    Ага, поэтому по тому, как их компоновать, целые книжки пишут, и потом обсуждают, что за SOLID такой, что за билдер, что за визитор
                    Да, и это всё из-за того, что сейчас заканчивается первая стадия ООП в духе С++ (слабо-инкапсулированные сильно-связанные объекты). Истинное ООП Следующий этап ООП, который должен был быть — в духе Smalltalk (сильно-инкапсулированные слабо-связанные объекты, а сам Smalltalk просто опередил свое время). Но этот этап пока отложился из-за хайпа ФП. Поэтому я предполагаю, что будет прыжок сразу на третий этап — синтез ООП+ФП, где объекты будут инкапсулировать в себе лишь состояния системы, а все действия будут выполняться в блоках в стиле ФП.


                    1. 0xd34df00d
                      20.06.2022 17:33
                      +2

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


                      1. nikolas78
                        20.06.2022 17:52

                        Ну а если поведение есть? А оно часто бывает.


                      1. 0xd34df00d
                        20.06.2022 17:59

                        Чтобы разговор был более предметным — какое именно поведение из той статьи вы имеете в виду? А то там, например, в поведение уходит и «неотрицательность остатка товаров».


                      1. nikolas78
                        20.06.2022 18:28

                        Такое поведение, которое вдруг запрещает действие с данными (по уважительной причине, заранее не ведомой инициатору изменения) и предписывает иные действия. Или же, в зависимости от входных данных, выполняются разные действия (вроде оператора switch case). И это все должно происходить внутри объекта независимо от того, кто его вызвал. Суть в том, что мы пишем поведение один раз и потом уже спокойно обращаемся к объекту, без необходимости проверять все кейсы на корректность — поэтому оно и должно быть внутри объекта…


                      1. 0xd34df00d
                        20.06.2022 18:38

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


                        У вас может быть


                        -- кишки Warehouse не экспортируются,
                        -- никто другой туда залезть не может
                        
                        module Warehouse
                        ( Warehouse
                        , emptyWarehouse
                        , takeGoods
                        , addGoods
                        ) where
                        
                        data Warehouse = WH Nat
                        
                        emptyWarehouse :: Warehouse
                        emptyWarehouse = WH 0
                        
                        addGoods :: Warehouse -> Nat -> Warehouse
                        addGoods (WH existing) delta = WH (existing + delta)
                        
                        removeGoods :: Warehouse -> Nat -> Maybe Warehouse
                        removeGoods (WH existing) delta =
                          if existing >= delta
                          then Just (WH (existing - delta))
                          else Nothing


                      1. nikolas78
                        20.06.2022 19:06

                        Тогда я вернусь к вашему предыдущему сообщению

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


                      1. 0xd34df00d
                        20.06.2022 19:14

                        Любое поведение, которое в ООП-языке вы бы выразили функцией-методом, и которое синтаксически привязано к этой структуре. Начиная от конструктора, инициализирующего структуру, и заканчивая гипотетическим bool Warehouse::removeGoods(Nat delta).


                      1. nikolas78
                        21.06.2022 23:04

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


                      1. 0xd34df00d
                        21.06.2022 23:42

                        Если не в терминологии современного ООП, то тогда вам придётся дать своё определение объекта. А зачем напрягаться, если можно просто рассуждать в терминах каких-то типов и функций на них?


                      1. nikolas78
                        22.06.2022 00:02

                        А зачем напрягаться, если можно просто рассуждать в терминах каких-то типов и функций на них?
                        Тонко…


                      1. Akon32
                        20.06.2022 18:39

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

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


                      1. 0xd34df00d
                        20.06.2022 18:44
                        +1

                        Ну да, как я и сказал, инкапсуляция в объекты — это такой кривенький способ реализовать какую-то модульную структуру.


                        Только свободные методы могут быть связаны сразу с несколькими объектами (и больше никаких вопросов, кому должен принадлежать метод applyDamage — class Weapon, class Person или class GameManager), могут возвращать что-то более точное, чем «объект + изменённый объект», и так далее.


                      1. siziyman
                        20.06.2022 19:54
                        +1

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

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


                    1. nin-jin
                      20.06.2022 17:39
                      +2

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


                      1. nikolas78
                        20.06.2022 17:58

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


                1. Cerberuser
                  20.06.2022 09:06
                  +13

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

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


            1. Erriour
              20.06.2022 12:27

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


            1. DonVietnam
              20.06.2022 14:39
              +1

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


              1. nin-jin
                20.06.2022 15:12
                +1

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


                1. 0xd34df00d
                  20.06.2022 17:38

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


                  1. nerlihmax
                    21.06.2022 10:19
                    +1

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


                1. DonVietnam
                  22.06.2022 12:18
                  -1

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


      1. dmitryvolochaev
        19.06.2022 21:31

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

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


        1. Kelbon Автор
          19.06.2022 21:49
          -4

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

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


          1. Chaos_Optima
            20.06.2022 12:45

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

            struct GraphicObject
            {
                virtual void draw() = 0;
            };
            
            std::vector<GraphicObject*> graphic_items;
            ....
            for (auto item : graphic_items)
                item->draw();


            1. naviUivan
              20.06.2022 14:11

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


              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.


              1. Chaos_Optima
                20.06.2022 14:46

                Интересно конечно, но на каждый метод создавать класс обёртку это вообще не удобно, также какие проблемы уходят при этом решении, по-моему их становится только больше, получается если код практически полностью полагается на вызовы виртуальных методов везде придётся писать 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 например, будет более логично.


                1. Kelbon Автор
                  20.06.2022 14:58

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

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


                  1. Chaos_Optima
                    20.06.2022 15:09
                    +2

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

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

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


              1. Kelbon Автор
                20.06.2022 14:53

                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();


                1. Chaos_Optima
                  20.06.2022 15:33
                  +2

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

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

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

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

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

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


                  1. Kelbon Автор
                    20.06.2022 15:47

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

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

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

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

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


                    1. Chaos_Optima
                      20.06.2022 16:03

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

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

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

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

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

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


                      1. Kelbon Автор
                        20.06.2022 16:08

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

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


                1. naviUivan
                  20.06.2022 16:02

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


              1. Kotofay
                20.06.2022 20:21
                +2

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

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

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

                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();


      1. Kotofay
        19.06.2022 23:03

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

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


        1. RNZ
          19.06.2022 23:56
          +3

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


          1. sandersru
            20.06.2022 00:26

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

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


            1. RNZ
              20.06.2022 00:56
              +5

              Неа, не получится. В 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/


              1. sandersru
                20.06.2022 01:04
                +2

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

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

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


                1. RNZ
                  20.06.2022 01:39
                  +2

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


                  1. sandersru
                    20.06.2022 13:38
                    +1

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

                    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 сделать таки можно.

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


                    1. sandersru
                      20.06.2022 14:35

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


                      1. RNZ
                        20.06.2022 22:59
                        +1

                        Да я смотрел доки по 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.


                      1. sandersru
                        20.06.2022 23:48

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

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

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

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

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

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

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

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

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


          1. Klems
            20.06.2022 09:29
            +5

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

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


            1. sandersru
              20.06.2022 14:22

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


              1. RNZ
                21.06.2022 02:02

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


                1. sandersru
                  21.06.2022 20:24

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

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

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

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

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

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

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


                  1. Kotofay
                    21.06.2022 22:32
                    +1

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

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


                    1. sandersru
                      21.06.2022 23:18

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

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

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


                  1. RNZ
                    22.06.2022 02:06

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


            1. RNZ
              21.06.2022 01:07

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


              1. sandersru
                21.06.2022 07:02

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


                1. nin-jin
                  21.06.2022 11:05
                  +1

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


                  1. sandersru
                    21.06.2022 11:38
                    -2

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


          1. Kotofay
            20.06.2022 11:20
            +1

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


            1. RNZ
              21.06.2022 01:27

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


              1. sandersru
                21.06.2022 06:53

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

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


        1. cepera_ang
          20.06.2022 02:17
          +16

          Сейчас мой код обработки матриц на Java работает быстрее чем идентичная по коду реализация на C

          А чем хорошая реализация на С?


          1. woodhead
            20.06.2022 06:38
            +3

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


            1. cepera_ang
              20.06.2022 07:10
              +2

              Да, всякие *BLAS’ы десятилетиями пилят, а MKL всё равно быстрее :)


              1. tzlom
                20.06.2022 08:51

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

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


          1. Kotofay
            20.06.2022 11:25
            -2

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

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

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

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


        1. tzlom
          20.06.2022 08:48

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

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

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


        1. Trrrrr
          20.06.2022 10:05
          +1

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


          1. Kotofay
            20.06.2022 11:39

            Не знаю как, но Graal оптимизирует такой код лучше чем С.


            1. 0xd34df00d
              20.06.2022 17:58

              Как компилируете и запускаете?


            1. sandersru
              20.06.2022 18:04

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

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

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


              1. Kotofay
                20.06.2022 20:49
                +1

                https://github.com/Kotofay/JavaVsC

                Test time : 1538
                Result : -1.0, -2.696002453891236
                


                1. sandersru
                  20.06.2022 21:03
                  +1

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


                  1. Kotofay
                    20.06.2022 21:32

                    Обновил

                    Test time : 1517
                    Result : -1.000000 -2.696002


                    1. sandersru
                      20.06.2022 21:34

                      Это в JVM режиме?


                      1. Kotofay
                        21.06.2022 14:23

                        Да.


                    1. 0xd34df00d
                      20.06.2022 22:11
                      +1

                      Потому что надо результат clock() делить на CLOCKS_PER_SEC, чтобы получить количество секунд. Ну и умножить потом на 1e3, чтобы получить количество миллисекунд, которое вам, судя по названию, даёт System.currentTimeMillis().


                      graal мне ставить лень, но с имеющимися у меня тулзами сишка таки быстрее где-то на четверть или треть:


                      % java --version
                      openjdk 17.0.2 2022-01-18
                      OpenJDK Runtime Environment Temurin-17.0.2+8 (build 17.0.2+8)
                      OpenJDK 64-Bit Server VM Temurin-17.0.2+8 (build 17.0.2+8, mixed mode, sharing)
                      % java Main.java
                      Test time : 1130
                      Result : -1.0, -2.696002453891236
                      % clang --version
                      clang version 14.0.1
                      Target: x86_64-pc-linux-gnu
                      Thread model: posix
                      InstalledDir: /usr/bin
                      % clang -O3 -march=native MainC.c -o main && ./main 
                      [...]
                      2 warnings generated.
                      Test time : 885.215000
                      Result : -1.000000 -2.696002
                      % clang -ffast-math -O3 -march=native MainC.c -o main && ./main
                      [...]
                      2 warnings generated.
                      Test time : 753.596000
                      Result : -1.000000 -2.696002


                      1. Kotofay
                        20.06.2022 22:20

                        Майкрософт реализует 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.


                      1. 0xd34df00d
                        21.06.2022 00:26

                        Майкрософт

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


                        И там присутствуют некоторые неприятные для С вещи.

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


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

                        Я, к сожалению, не нашёл graalvm в репах, а ставить в обход пакетного менеджера (или искать оверлей) лень. Но можно посмотреть, как быстро чистая java отрабатывает у вас, и прикинуть по соотношению.


                      1. Kotofay
                        21.06.2022 14:25

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

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

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


                      1. 0xd34df00d
                        21.06.2022 19:49

                        Почему не может? Если он может себе доказать, что индексов больше 31 не бывает (а информации для этого у него достаточно, n у вас всегда одно и то же и вообще final), то проверку на размер входных массивов он может вынести в начало функции, а не в каждую итерацию, что сильно снизит её стоимость, позволит векторизовать код, и так далее.


                      1. Kotofay
                        21.06.2022 22:43

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

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

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

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


                      1. 0xd34df00d
                        21.06.2022 23:41

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

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


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

                        Что значит «исключительно надёжным»? Добавить в начало функции проверки на длину и использовать дальше operator[] вместо at — это исключительная надёжность или уже нет?


                      1. Kotofay
                        21.06.2022 14:30

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

                        Clang:

                        Test time : 1237.000000
                        Result : -1.000000 -2.696002


                      1. Kotofay
                        21.06.2022 16:42
                        -2

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

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

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

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


                      1. 0xd34df00d
                        21.06.2022 19:50

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

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


                        static void lu_solve1( double a[ 32 ][ 32 ], int n, int ipvt[46], double b[32]) 

                        (да, технически это не совсем размер, но неважно)


                      1. Kotofay
                        21.06.2022 22:35

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

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

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


                      1. sandersru
                        21.06.2022 08:41

                        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" еще несколько штук. Тут от задач и тюнинга зависит


                      1. Kotofay
                        21.06.2022 10:40

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

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


                1. nin-jin
                  20.06.2022 23:03

                  1. Kotofay
                    20.06.2022 23:23

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


                    1. nin-jin
                      20.06.2022 23:49
                      +1

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

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


                      1. Kotofay
                        21.06.2022 10:42
                        -1

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

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

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

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

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


                      1. nin-jin
                        21.06.2022 11:13

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


                      1. Kotofay
                        21.06.2022 11:30
                        -2

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

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


                      1. nin-jin
                        21.06.2022 13:58

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


                      1. Kotofay
                        21.06.2022 14:12
                        -1

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

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

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


                      1. nin-jin
                        21.06.2022 14:53
                        +1

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

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


                      1. Kotofay
                        21.06.2022 16:06
                        -2

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

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

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


                1. Akon32
                  21.06.2022 17:51
                  +1

                  А если прогревать 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 лучше, и то не всегда. Дисперсии не измерял.


            1. Trrrrr
              20.06.2022 19:58
              +1

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

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


      1. ris58h
        20.06.2022 11:05

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

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



      1. siziyman
        20.06.2022 11:16
        +1

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


      1. RomanSt
        20.06.2022 12:11

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


        1. xXxVano
          20.06.2022 22:17
          +4

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


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


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


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


    1. Kelbon Автор
      19.06.2022 20:54

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


      1. KoCMoHaBT61
        19.06.2022 21:03
        +3

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

        Например...


        1. Kelbon Автор
          19.06.2022 21:10
          +4

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


        1. F0iL
          19.06.2022 21:21
          +14

          В C++ проблема в том, что большинство таких type puning трюков с union нарушает active member rule, что согласно стандарту является undefined behaviour. То есть вы создали bsshort, присвоили полю val какое-то значение, полученное от железки... а потом обращаетесь к l или h, и опачки! - у вас код, являющимся некорректным с точки зрения стандарта, и компилятор имеет право сотворить из него все что угодно


          1. Zenitchik
            20.06.2022 00:11
            +6

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

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


            1. F0iL
              20.06.2022 00:26
              +3

              Изначально union придумали в C. В C такие трюки делать можно.

              Когда его переняли в C++, то решили, что так нельзя. Но там union, например, используется, например, в некоторых реализациях variant-типов. А для трюков в C++20 наконец-то завезли std::bit_cast.


          1. mentin
            20.06.2022 03:28

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


            1. F0iL
              20.06.2022 10:19
              +2

              Бегло перечитал эту часть стандарта, исключение там есть только дли типов с общей начальной структурой (не уверен, как правильно перевести "common initial sequences"), но type puning это все равно не разрешает, а каких-либо исключений для POS'ов там что-то не видно. Как пишут на SO, некоторые компиляторы в виде исключения разрешают подобные касты как неофициальное расширения стандарта, но тут уж без каких-то гарантий переносимости в целом.


              1. mentin
                21.06.2022 01:26

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


    1. akurilov
      19.06.2022 22:32
      -3

      Java + GraalVM = golang


    1. Akon32
      20.06.2022 09:49
      +2

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


      1. sandersru
        20.06.2022 13:28

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


        1. Akon32
          20.06.2022 14:36
          +2

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

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

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


          1. sandersru
            20.06.2022 14:41

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

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


            1. Akon32
              20.06.2022 14:59
              +2

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

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


              1. sandersru
                20.06.2022 18:45

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

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

                И вперёд...


                1. Akon32
                  20.06.2022 20:15

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

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


          1. sandersru
            20.06.2022 14:53

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

            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);


            1. Akon32
              20.06.2022 15:17
              +1

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


              1. sandersru
                20.06.2022 15:21

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


                1. Akon32
                  20.06.2022 20:02
                  +2

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


            1. xXxVano
              20.06.2022 22:25

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


              1. sandersru
                21.06.2022 00:04

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

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


                1. xXxVano
                  21.06.2022 00:27
                  +1

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


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


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


                  1. sandersru
                    21.06.2022 07:06
                    -1

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

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


                    1. xXxVano
                      21.06.2022 14:01

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


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


                      1. sandersru
                        21.06.2022 16:14

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

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


                      1. xXxVano
                        21.06.2022 16:35
                        +1

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


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


                      1. sandersru
                        21.06.2022 20:06

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

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

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

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

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


                      1. xXxVano
                        21.06.2022 22:26

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


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


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


                      1. Akon32
                        22.06.2022 05:22

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


    1. event1
      20.06.2022 15:52
      +4

      И да, 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"

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


  1. DartfoL
    19.06.2022 20:51
    +15

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

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


  1. MFilonen2
    19.06.2022 21:25
    +3

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


    1. sandersru
      19.06.2022 21:30
      +33

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


    1. CrashLogger
      20.06.2022 10:57
      +2

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


    1. playermet
      20.06.2022 11:27
      +2

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

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


  1. gotoxy
    19.06.2022 21:28
    +23

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

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

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


    1. Kelbon Автор
      19.06.2022 21:33
      -12

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

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


      1. gotoxy
        19.06.2022 21:56
        +3

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


      1. Grepshe
        19.06.2022 21:59
        +17

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


        1. Kelbon Автор
          19.06.2022 22:00
          -7

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


          1. Grepshe
            19.06.2022 22:04
            +21

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


            1. Kelbon Автор
              19.06.2022 22:21
              -7

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


              1. zvszvs
                20.06.2022 09:09
                +4

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


    1. sandersru
      19.06.2022 21:34
      +2

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

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


      1. gotoxy
        19.06.2022 22:05
        +3

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


  1. vanxant
    19.06.2022 21:32
    +10

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


  1. sergio_nsk
    19.06.2022 21:53
    +9

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


  1. Izaron
    19.06.2022 22:00
    +32

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

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

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

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

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

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

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

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

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

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


    1. Kelbon Автор
      19.06.2022 22:08
      -10

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

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

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

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

      5. Cmake

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

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


      1. tandzan
        19.06.2022 22:34
        +24

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


        1. BeardedBeaver
          19.06.2022 22:46
          +8

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


          1. majstar_Zubr
            20.06.2022 11:18
            +1

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


            1. AnthonyMikh
              20.06.2022 13:37
              +1

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


      1. Izaron
        19.06.2022 22:38
        +11

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

        Ситуация из разряда "жопа есть, а слова нету". Почему нельзя удалить из стандарта ставший ненужным с 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 - у них все есть и они все могут.


        1. BeardedBeaver
          19.06.2022 22:49
          +2

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

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


          1. AnthonyMikh
            20.06.2022 13:39

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


        1. NightShad0w
          19.06.2022 23:04

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


        1. Kelbon Автор
          19.06.2022 23:22
          -2

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

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

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

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


          1. playermet
            20.06.2022 11:52
            +2

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

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


            1. cepera_ang
              20.06.2022 17:53

              А потом всё равно кто-нибудь напишет свой квадратичный парсер и будет 10кб джсон по пять минут грузиться.


            1. 0xd34df00d
              20.06.2022 18:26

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


    1. nin-jin
      20.06.2022 09:16
      -1

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


  1. victor_1212
    19.06.2022 22:04
    +6

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


    1. Revertis
      20.06.2022 01:32
      +10

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


      1. victor_1212
        20.06.2022 02:57
        +1

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

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


  1. nmrulin
    19.06.2022 22:52

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

    typedef struct {

    union

    {

    unsigned int address;

    unsigned char bytes[4];

    }

    } ipv4_address;

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

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


    1. BugM
      19.06.2022 23:04
      +11

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

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


    1. sandersru
      19.06.2022 23:06
      +3

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


    1. Kelbon Автор
      19.06.2022 23:13
      +5

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

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


      1. nmrulin
        20.06.2022 13:03

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

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

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

        ----

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


    1. F0iL
      20.06.2022 00:00
      +9

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

      Если вы храните адрес в виде union чтобы записать значение в поле одного типа, а потом прочесть его из поля другого типа, то я вас расстрою: в C++ согласно стандарту так делать нельзя.


      1. nin-jin
        20.06.2022 09:24
        +1

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


        1. F0iL
          20.06.2022 10:23
          +2

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


          1. nin-jin
            20.06.2022 10:59

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


            1. F0iL
              20.06.2022 11:04
              +2

              Пункт 11.5 стандарта языка прямо говорит, что для структурирования буфера юниоин использовать нельзя:

              At most one of the non-static data members of an object of union type can be active at any time

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

              Правда, учитывая, что начиная с C++20 в языке появился std::bit_cast, сомневаюсь, что "мне нужен юнион для того чтобы кастовать буфер к структуре и обратно" будет хорошим аргументом :)


              1. nin-jin
                20.06.2022 12:00

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

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


                1. F0iL
                  20.06.2022 12:28

                  В вашей цитате нет ничего про "нельзя"

                  Я просто ее не до конца скопипастил, там ещё потом идёт "...that is, the value of at most one of the non-static data members can be stored in a union at any time".

                  Если вы не верите тому, что говорю я, послушайте вот это отличное выступление с CppCon про каламбур типизации в современном C++.

                  Вы, конечно, можете продолжать спорить с очевидным, вот только корректным в соответствии со стандартом такой C++-код от этого не станет.


    1. tsvetkovpa
      20.06.2022 09:07
      +1

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

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


      1. nmrulin
        20.06.2022 13:34

        Спасибо , может мне пригодится. Интересно, как он внутри работает. В коде 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) ));}


        1. Kotofay
          20.06.2022 21:06

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

                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;
                }


          1. sandersru
            20.06.2022 21:37

            Самое зло с 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 дня назад. Тот же самый код - уже не работает :)

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


            1. Kotofay
              20.06.2022 22:05
              +1

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

              Без "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;
              };


              1. sandersru
                21.06.2022 07:14

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

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


                1. Kotofay
                  21.06.2022 11:20

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

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

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

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

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

                  struct foo {

                  uint8_t bar;

                  uint8_t baz;

                  } __attribute__((packed));


    1. event1
      20.06.2022 15:58
      +2

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


    1. rblaze
      21.06.2022 18:27

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


  1. NightShad0w
    19.06.2022 22:56
    +12

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


    Про 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 вот только для функций используется, и это прекрасно. Хочешь применить алгоритм к аргументам — используешь свободные функции, хочешь запускать сложную машинерию скрыто от глаз вызывающего кода(и для хитрого продакшен кода, и для тестов, почему нет) — методы наше все.


    1. Kelbon Автор
      19.06.2022 23:07

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

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


    1. AnthonyMikh
      20.06.2022 13:43
      +1

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

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


      1. NightShad0w
        21.06.2022 19:05
        +1

        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.


        1. DarkEld3r
          21.06.2022 21:54
          +2

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


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

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


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

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


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


  1. zakker
    19.06.2022 23:43
    +2

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


    1. Kelbon Автор
      19.06.2022 23:53
      -1

      Посмотрите на примеры с гитхаба в том же пункте. Нет, мне не нужны везде 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)


      1. Ritan
        20.06.2022 01:57
        +6

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


        1. Kelbon Автор
          20.06.2022 08:29
          +1

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

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

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

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


      1. zakker
        20.06.2022 10:28
        +2

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

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


  1. Helltraitor
    19.06.2022 23:53
    +6

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


    1. Kelbon Автор
      19.06.2022 23:54
      -2

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


      1. domix32
        20.06.2022 01:06
        +1

        аби

        А у кого оно было?


        1. nin-jin
          20.06.2022 09:33

          1. domix32
            20.06.2022 10:49

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


      1. Revertis
        20.06.2022 01:34
        +4

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


  1. domix32
    20.06.2022 01:05
    +1

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


  1. PkXwmpgN
    20.06.2022 01:50
    +5

    Следующий код полностью заменяет юнион, не имеет никакого оверхеда относительно него и имеет более понятный пользователю интерфейс (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 и вот это у вас будет в каждом юните трансляции?


    1. Kelbon Автор
      20.06.2022 08:35
      -5

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

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


      1. PkXwmpgN
        20.06.2022 09:13
        +1

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

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

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


        1. Kelbon Автор
          20.06.2022 09:44

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

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


          1. PkXwmpgN
            20.06.2022 10:04
            +1

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

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

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

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


            1. Kelbon Автор
              20.06.2022 10:46

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


              1. PkXwmpgN
                20.06.2022 11:03

                1. Kelbon Автор
                  20.06.2022 11:22

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


    1. Kelbon Автор
      20.06.2022 08:38
      -2

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

      https://godbolt.org/z/oW4zEc7hK

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


      1. Cerberuser
        20.06.2022 08:57
        +2

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

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


        1. Kelbon Автор
          20.06.2022 09:02

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

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

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

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


          1. Cerberuser
            20.06.2022 09:05
            +2

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


            1. Kelbon Автор
              20.06.2022 09:52

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


              1. Cerberuser
                20.06.2022 09:55
                +2

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


                1. Kelbon Автор
                  20.06.2022 11:03

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


              1. 0xd34df00d
                20.06.2022 18:30
                +1

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


                Условно, я могу is_same реализовать через специализацию шаблонов, а могу — через интринсик в моём компиляторе. Непереносимость второго варианта кода на любой другой компилятор не делает любой другой компилятор несовместимым с плюсами — она делает мою реализацию несовместимой со стандартным C++.


                Да, в стандарте есть некоторые вещи, которые нельзя сделать иначе как интринсиками, но make_integer_sequence не из них.


                1. Kelbon Автор
                  20.06.2022 18:34

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


      1. PkXwmpgN
        21.06.2022 09:06
        +1

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

        Хорошо, не нравиться 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'ы?


  1. suns
    20.06.2022 08:17
    +4

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

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


    1. Kelbon Автор
      20.06.2022 08:33
      -3

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

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

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


    1. Kelbon Автор
      21.06.2022 07:35

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


  1. Vitaly83vvp
    20.06.2022 08:27
    +2

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


    1. lastmac
      20.06.2022 08:48
      +2

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


      1. Kelbon Автор
        20.06.2022 09:10
        +6

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


        1. lastmac
          21.06.2022 00:50
          -1

          Уши у вас холодные (с)


  1. lncubus
    20.06.2022 08:32
    -1

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

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


    1. Kelbon Автор
      20.06.2022 08:32

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


  1. eao197
    20.06.2022 08:47
    +1

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

    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. Статью целиком не осилил.


  1. DX168B
    20.06.2022 08:54
    -1

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


  1. NeoCode
    20.06.2022 10:02
    +7

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

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

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

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

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


    1. staticmain
      20.06.2022 12:41

      Автор предлагает убрать массивы, но сделать *(a + n)

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

      Кажется человек не очень понимает, что есть [ ] на самом деле.


      1. Kelbon Автор
        20.06.2022 12:57

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

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


        1. playermet
          20.06.2022 13:23

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

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

          foo = 5[bar];


  1. HEXFFFFFFFF
    20.06.2022 10:02
    -1

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


    1. F0iL
      20.06.2022 10:45
      +3

      Вариантов реализации type-puning'а на C++ чуть ли не десяток. Проблема в том, что практически все из них (в том числе и ваш любимый вариант с union) являются некорректными (а следовательно, опасными) с точки зрения стандарта языка.
      Поэтому среди них единственными правильными и переносимыми являются std::bit_cast (начиная с С++20) и std::memcpy(&destination, &x, sizeof(x). Современные компиляторы трюк с memcpy легко узнают и оптимизируют как надо, поэтому по факту в плане производительности разницы с union никакой.


    1. Gumanoid
      20.06.2022 11:05
      +1

      Подобное преобразование нелегально в C++, но т.к. многие его используют по привычке из C, то компиляторы предпочитают не ломать такой код: https://stackoverflow.com/questions/11373203/accessing-inactive-union-member-and-undefined-behavior


  1. Static_electro
    20.06.2022 10:21

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

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

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


    1. Kelbon Автор
      20.06.2022 11:04
      +2

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


      1. Static_electro
        20.06.2022 11:46

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


  1. FloorZ
    20.06.2022 10:35

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


    1. Kelbon Автор
      20.06.2022 11:06
      +2

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


      1. nin-jin
        20.06.2022 12:09
        -4

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


        1. Kelbon Автор
          20.06.2022 12:33
          +3

          это даже не С++


          1. nin-jin
            20.06.2022 13:52
            -3

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


      1. F0iL
        20.06.2022 12:51

        boost::variant2 использует union'ы. Правда, весьма своеобразно :) Оригинальная же реализация, как вы и сказали, работает через массив байт.


  1. AnthonyMikh
    20.06.2022 13:55
    +2

    Реализация массива без массива:

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


    6 — typedef

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


    1. Kelbon Автор
      20.06.2022 14:21

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


  1. truthfinder
    20.06.2022 16:37

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


    1. Soukhinov
      20.06.2022 20:03

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


  1. Soukhinov
    20.06.2022 19:51

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

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


  1. maeris
    20.06.2022 20:11

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

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

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


    1. maeris
      20.06.2022 20:19

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


    1. Kelbon Автор
      21.06.2022 07:24

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


  1. nickolaym
    21.06.2022 23:26

    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) Гвидо ван Россум, отстаньте уже, пожалуйста. Над питоном в младенчестве надругались, это не значит, что надо над всеми остальными языками то же самое делать.


    1. AnthonyMikh
      22.06.2022 01:55
      +1

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

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


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

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


    1. Kelbon Автор
      22.06.2022 13:45

      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 и это теперь реальность плюсов