Перемещение — прошлый век! Альтернативы std::move в «C++ будущего»

    Каждый раз, когда мы пишем класс, управляющий ресурсами, мы задумываемся о том, что, скорее всего, для него придётся писать move-конструктор и move-присваивание. Ведь иначе объекты такого типа становятся неуклюжими, как std::mutex, ими тяжело пользоваться на практике: ни вернуть из функции, ни передать в функцию по значению, ни положить в вектор — а если положить его в другой класс как один из членов, то тот класс также «заболевает».


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


    Для абстрактных типов данных пустое состояние обычно бессмысленно — если у объекта украли его ресурсы, то он не сможет выполнять свои обычные функции. Но мы вынуждены это делать, чтобы реализовать move-семантику. Для некоторых типов пустое состояние недопустимо: open_file (в противовес теоретическому file), not_null_unique_ptr<T> (в противовес unique_ptr<T>).


    Говоря словами Arthur O'Dwyer, мы заказывали телепорт, а нам дали «вас клонируют и убивают первоначальную копию». Чтобы вернуть себе телепорт, проходите под кат!


    Я опишу несколько предложений к стандарту C++, которые объединены одной темой: свести к минимуму число перемещений. Но для начала, ещё раз: почему меня должно это заботить?


    1. Я не хочу тратить усилия на реализацию move-семантики для всех типов, владеющих ресурсами
    2. Я не хочу иметь во всех своих типах пустое состояние. Часто оно не к месту. Бывает, что его сложно или невозможно добавить. И всегда это лишние усилия на поддержку
    3. Даже если move-семантика реализуема, она может быть непозволительна из-за того, что мы хотим раздать указатели на этот объект
    4. Даже если перемещение допустимо, будет затрачено время на то, чтобы «занулить» первоначальный объект, и потом удалить его по всем правилам. И нет, компиляторы не могут это оптимизировать: раз, два

    Итак, поехали.


    P1144: Trivially relocatable


    Это предложение к стандарту, за авторством Arthur O'Dwyer, добавляет новый атрибут [[trivially_relocatable]], которым можно пометить типы, которые можно передавать более эффективно, чем через move. А именно, мы копируем объект на новое место через memcpy и забываем про первоначальный объект, не вызывая для него деструктор. Правда, таким образом нельзя перемещать локальные переменные, так как компилятор вызывает их деструкторы за нас, не спрашивая, и у этой проблемы нет простого решения.


    Атрибут можно применить к вашим классам при их определении. На практике атрибут будет нужен нечасто: компилятор автоматически помечает класс [[trivially_relocatable]], если все его члены являются таковыми, и вы не определили кастомные move-конструктор с деструктором (rule of zero). Классы стандартной библиотеки будут помечены [[trivially_relocatable]] для повышения производительности существующего кода, однако какие именно будут помечены, оставляется на усмотрение реализации. std::vector и прочие будут использовать новую функцию relocate_at, которая делает relocation или move, в зависимости от того, что тип поддерживает.


    template <typename T>
    class [[trivially_relocatable]] unique_ptr { ... };
    
    std::vector<unique_ptr<widget>> v;
    for (auto x : ...) {
      // Старые unique_ptr перемещаются через relocation, а не move
      v.push_back(std::make_unique<widget>(x));
    }

    С proposal есть несколько проблем, которые обсуждаются:


    • Можно пометить класс как [[trivially_relocatable]], даже если его члены таковыми не являются. Например, таким образом можно сломать std::mutex, обернув его в свой [[trivially_relocatable]] класс
    • У класса всё равно должен быть реализован конструктор копирования (будем добиваться отмены ограничения)
    • Trivially relocatable типы всё равно нельзя передавать в регистрах. Например, std::unique_ptr<T> по-прежнему будет передаваться в функции как указатель на указатель

    P2025: Guaranteed NRVO


    Рассмотренный выше proposal применим тогда, когда объект приходится перемещать, но можно сделать это эффективнее, чем сейчас. Тем не менее, в том случае указатели на объект всё равно «ломаются». В отличие от него, P2025 позволяет устранить саму причину перемещений в некоторых случаях.


    C++17 исключил перемещения, когда мы вычисляем значение в return и тут же возвращаем его. Это называется Return Value Optimization (RVO). P2025 исключает также перемещения, когда мы возвращаем локальную переменную (NRVO). При этом она может быть не-перемещаемой, вроде std::mutex или наших абстрактных типов данных:


    widget setup_widget(int x) {
      return widget(x);  // OK, C++17
    }
    
    widget setup_widget(int x) {
      auto w = widget(x);
      w.set_y(process(x));
      return w;  // OK, P2025
    }

    Кстати, proposal мой :)


    P0927: Lazy parameters


    Фактически, предлагается аналог @autoclosure из Swift. Параметр функции может быть помечен специальным образом, чтобы соответствующий аргумент при вызове автоматически оборачивался в лямбду. Перемещение при таком способе передачи параметров не происходит, объект создаётся сразу там, где нужно:


    void vector<T>::super_emplace_back([] -> T value) {
      void* p = reserve_memory();
      new (p) T(value());
    }
    
    vector<widget> v;
    v.super_emplace_back(widget());  // нет move
    v.super_emplace_back([&] { return widget(); });  // под капотом

    P0573: Abbreviated lambdas


    Это решение более общее, чем предыдущее, и затрагивает также другие проблемные темы. Сокращённый синтаксис лямбда-выражений сделает работу с коллекциями и «ленивыми параметрами» в C++ такой же приятной, как и в нормальных других языках. Правда, с синтаксисом P0573 есть проблемы, но я готов предложить несколько других вариантов, к тому же, более коротких:


    // Текущий синтаксис
    auto add = [&](auto&& x, auto&& y) { return x + y; };
    auto dbl = [&](auto&& x) { return x * 2; };
    auto life = [&] { return 42; };
    
    // P0573
    auto add = [&](x, y) => x + y;
    auto dbl = [&](x) => x * 2;
    auto life = [&]() => 42;
    
    // Мой #1: из Rust
    auto add = |x, y| x + y;
    auto dbl = |x| x * 2;
    auto life = || 42;
    
    // Мой #2
    auto add = x y: x + y;
    auto dbl = x: x * 2;
    auto life = :42;

    На этом всё! Желаю всем предложениям исправить пробелы и быть принятыми в C++23. Любые вопросы, замечания, пожелания оставляйте в комментариях.

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

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

      +7

      Мне кажется, основная причина в отсутствии destructive move, а не тривиальности перемещения. Но добиться "забывания" старых местоположений по сути невозможно, т.к. это сломает кучу существующего кода. Семантика конструкторов, деструкторов, перемещений, копий и т.п. накручена поверх value семантики из С.
      Дополнительная проблема — все эти нововведения призваны ограничивать, а не освобождать. Т.е. чем правильней вы хотите описать поведение типа, тем больше писанины, правил 0-1-3-5-42, атрибутов и т.п. вам надо помнить. А по умолчанию получается POD.
      Конечно, "вирусность" нетривиальных копирования и перемещения помогает. Но не сильно. Потому что действие по умолчанию — копирование, перемещение требует отдельного вызова обёртки. Базовых вещей вроде resource wrapper, finalizer в стандартной библиотеке нет до сих пор. Или пишите свой велосипед с риском ошибиться, или ищите в другом месте.
      Касательно же Rust, один паттерн оттуда уж точно стоит взять на вооружение. Это copy method, когда создание копии происходит только через явный вызов отдельного метода.

        +1

        Полностью согласен. Trivially relocatable — калька с Rust, просто поняли, что там намного лучше. Abbreviated lambdas разрабатываются с учётом того, чтобы не копировать неявным образом параметры или результат; про обычные функции там тоже подумали. Было бы круто ещё иметь короткие не-копирующие объявления переменных:


        x := f();               // станет?
        decltype(f()) x = f();  // сейчас
          0

          А в чем смысл? Синтаксический сахар вместо auto или decltype(auto)?

            +1

            Примерно. С decltype(auto) тоже есть проблема, об которую можно споткнуться: decltype(x.y) выдаст тип T, а не T&, если поле y типа T. Тут спасёт новая конструкция, которая корректно обработает такие случаи.

              +1

              Но ведь f() в вашем примере в любом случае возвращает некий конкретный тип, не очень понимаю, причём тут проблема с типом полей. Можете пояснить на конкретном примере, какую именно проблему призваны решить эти "короткие не-копирующие объявления", кроме сокращения количества печатаемых символов?

                +1

                Основная идея — сделать не-копирующие объявления короче копирующих. Второстепенная — корректность:


                struct S { int x; }
                S s{42};
                decltype(auto) y = s.x;  // тип - просто int

                Причина — неочевидное поведение decltype в целом и decltype(auto) в частности:
                decltype(s.x) = int
                decltype((s.x)) = int&


                Убедитесь сами.

                  +1
                  Да, я в курсе такого поведения, оно вполне себе описанное. То есть, если я правильно понял, что вы хотите, то вы хотите такой универсальный синтаксис, который бы всегда делал автоматический ссылочный тип, но с сохранением CV, что-то типа несуществующего сейчас decltype(auto&&)? Ну, не знаю, городить для этого специальный отдельный синтаксис с ":=" — это такое. Лично я бы предпочел визуально видеть, что я имею дело именно со ссылкой, а тут это неочевидно. Я бы предпочел как раз что-то вроде decltype(auto&&), хотя это и «многабукав», признаю.
                    +1

                    Внезапно понял, что auto&& сработает. Для возвращаемого типа функции это недопустимо, а для локальной переменной, где надо, сработает lifetime extension. Так что зря я нагородил :facepalm:


                    Хотя если мы потом вернём эту переменную с lifetime extension, то NRVO не сработает. Но тогда уж лучше прописать разрешение на это в NRVO.

                      +3
                      Да, в случае локальной переменной auto&& должно быть вполне достаточно. Вот так вот и делается «синтаксический мусор» © selrorener, хехе.
            –4
            Trivially relocatable — калька с Rust

            Основания. Хотя глупо это спрашивать — Trivially relocatable — это базовое свойство всего в си. Когда там это было никакого раста в принципе не существовало, но в любом случае я жду оснований. Да и никаких понятий "relocatable" в расте в принципе нет — там голые структуры взятые из llvm(читай си).


            Т.е. нет никакие конструкторов, нету наследования, прав доступа, инициализаторов и прочего.


            Было бы круто ещё иметь короткие не-копирующие объявления переменных:

            Зачем этот мусор в языке?

              +1
              Основания. Хотя глупо это спрашивать — Trivially relocatable — это базовое свойство всего в си. Когда там это было никакого раста в принципе не существовало, но в любом случае я жду оснований.

              В C, но не в C++. Когда при принятии C++11 думали, как уменьшить число копирований, почему-то результатом стала move-семантика. А сейчас обнаруживается, что в 99% случаев-то на самом деле нужен destructive move (или relocation, как его сейчас называют). Хотя в комитете сидят люди далеко не глупые. Значит, со времён C++11 тенденции в языках программирования поменялись. Насколько я знаю, именно Rust привнёс концепцию destructive move в массы.


              Да и никаких понятий "relocatable" в расте в принципе нет

              Relocatable — это понятие из C++. Аналог в Rust называется просто move и записывается как присваивание.


              Зачем этот мусор в языке?

              Эта конструкция отменила бы копирование как поведение "по умолчанию". Но я особенно над этим не думал. Возможно, да, фигню сморозил.

                –11

                Я вот не понимаю — откуда вы берёте все эти глупости?


                В C, но не в C++.

                Этого уже достаточно, зачем вы везде пихаете свою второсортную вторичную стриптуху? Что-то потом засыпаться?


                К тому же, это так же работает и для С++. Но это не работает для сложных типов, но об этом позже.


                Когда при принятии C++11 думали, как уменьшить число копирований, почему-то результатом стала move-семантика.

                Полная и нелепая чушь, опять вы повторяете эту нелепость уже 10 лет. Вы не способны чему-то учиться.


                move — это и есть копирование, и копирование всегда было таким. Другое дело, что в C++ с созданием raii копирование стало иметь другую семантику и копирование перестало быть копированием, коим оно является по умолчанию и коим является в си.


                В связи с этим и появилось move, которая является тем самым копированием, которое было до раии и которое было в си.


                А сейчас обнаруживается, что в 99% случаев-то на самом деле нужен destructive move (или relocation, как его сейчас называют).

                Полная и нелепая чушь. Опять ретрансляция какой-то нелепой пропаганды какой-то нелепой скриптухи. "Trivially relocatable" не имеет никакого отношения к "destructive move" и прочей чуши.


                Никакого "destructive move" в принципе не может существовать. Это костыль, который придуман в скриптухах типа раста. Где взята банальная сишная семантика, а далее примитивным статическим анализом поверх тысяч ограничений и ручного труда адептов — была реализована.


                Т.е. в скриптухи нету никаких конструкторов, нету никаких деструкторов, нету никакого времени жизни. Кто угодно имеет полный доступ к объекту, как это было всегда в си.


                К этому просто прикручена drop-семантика. Схема там проста — когда есть одна ссылка и область видимости кончается — срабатывает drop. Если ссылка не одна — мутировать объект нельзя. drop является мутацией.


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


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


                По поводу "Trivially relocatable" — его смысл в в следующем. Поды можно так же копировать как угодно, как в си. Но если мы навешиваем на этот под конструкторы/деструкторы — просто так копировать что-то уже нельзя. Семантика предполагает их вызов, за редкими исключениями.


                Да вот, "Trivially relocatable" — это то, что позволяет сказать. "да, у меня есть конструкторы/деструкторы. Но я позволяю себя копировать, не вызывая их у копий, реализация объекта под хинтом предполагает это и никаких проблем не возникнет".


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


                Если ещё проще. Подобная схема в рамках раии призвана куда-то наверх засунуть raii-объект, а далее использовать тот же объект как не-раии view. Но здесь можно просто использовать view. С++ это не примитивная скриптуха типа раста, где это невозможно сделать и приходится таскать оригинальные объекты.


                Поэтому мы можем использовать raii-объекты как стореджи, а далее использовать view-объекты для доступа. Правда непонятно почему не подходят ссылки — почти везде для данного кейса их можно использовать. Я понимаю раст-скриптуху, где ссылки это муоср неудобный, но в С++ с этим проблем нет. Семантика, конечно же, сложная. Но на то он С++, а не примитивная скриптуха.


                Поэтому если не хочется перемещать(читай копировать с вызовом не раишных конструктуоров/деструкторов) raii-объекты и есть гарантия, что изначальный объект не сдохнет быстрее копии. Можно просто сделать ptr_view, в который любой sp может каститься. А он pod — он уже копируется как нужно.


                Хотя в комитете сидят люди далеко не глупые. Значит, со времён C++11 тенденции в языках программирования поменялись. Насколько я знаю, именно Rust привнёс концепцию destructive move в массы.

                Relocatable — это понятие из C++. Аналог в Rust называется просто move и записывается как присваивание.

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


                Почему вам так трудно это понять. В скриптухе просто поды из сишки. Всё. Никаких конструкторов, дестркторов и времени жизни нет. В С++ это есть и уже поверх этого создано raii.


                Скриптуха взяла raii из крестов, но оно там совершенно другого вида. Как бы попроще объяснить. В общем, если на примере с си. Это просто указатели, которые можно как угодно копировать. А потом магическим образом где-то компилятор вставить free.


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


                Эта конструкция отменила бы копирование как поведение "по умолчанию".

                Копирование и так поведение по умолчанию. У вас с этим проблемы большие. Есть язык — его логика совершенно иная. Не раишные. Базовая семантика языка другая, а раии это просто некая логика прикрученная сверху.


                Ненужно это путать. И ненужно пихать это на уровень языка.


                Но я особенно над этим не думал. Возможно, да, фигню сморозил.

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


                Допустим, чушь про лямбды. Лямбды в раст-скриптухи — это паста []{} — базового синтаксиса из крестов и руби. Т.е. в скриптуха слишком примитивная — там ненужен список захватов. Точно так же в скриптухи слабая система типов — типов там вообще нет. Поэтому там и ненужны типы.


                А вообще подобные примеры — позор:


                auto add = |x, y| x + y;
                auto dbl = |x| x * 2; 

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


                auto add = _ + _;
                auto dbl = _ * 2; 
                  0
                  Насколько я знаю, именно Rust привнёс концепцию destructive move в массы.
                  ну нет же. Над мув-семантикой в плюсах начинали работать задолго до того, как раст начали разрабатывать. Проблема только в том, что надстроить destructive move поверх существующего кода не получилось
                    +1
                    Оно и далеко не всегда надо, откровенно говоря. Как тут уже было метко подмечено, если ты перелил чай из чашки в желудок, то это не означает, что чашку надо немедленно уничтожить. Зачастую гораздо дешевле просто налить в нее новый чай.
                      0
                      по стандарту, после move out от класса можно ожидать только что он будет в «каком-то корректном» состоянии. И т.к. это состояние может не совпадать с ожиданиями, лучше после moved out переменные не использовать. В конце концов, для подавляющего большинства movable классов пара «удаление moved out переменной и создание новой» достаточно дешевые.

                      Вот мы и приходим к актуальности destructive move
                        +1
                        Их вполне можно использовать для того, чтобы переместить в такую переменную что-то новое, затем проделать некие операции, затем переместить результат куда-то еще, затем опять переместить в нее что-то новое, и так далее — скажем, в нагруженном цикле. Как бы ни было дешево «удаление и создание новой», это все-таки зачастую не бесплатно.
                          0
                          в большинстве случаев либо все эти операции (удаление moved out, move constructor/assignment и т.д.) дешевые (дернуть пару указателей), либо дорогие.

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

                            В том-то и дело. Вот тут реализованы два цикла:

                            godbolt.org/z/FkXThD

                            Первый — это симуляция «destructive move» через использование локальной переменной в теле цикла, второй — оптимизированный, через «повторное использование чашки». Представим, что циклы очень нагруженные. Какой вариант в этом случае предпочтительнее? Обратите внимание, что в первом варианте постоянно идет совершенно лишний вызов деструктора, который может быть недешевым, во втором — делаются только две операции перемещения, и все (ну, по идее, конечно, предполагается, что между ними с foo там должно еще что-то делаться полезное, иначе зачем это все).
                              +1

                              Если бы destructive move была и правда destructive — деструктор бы в первом цикле не вызывался.

                                +1
                                Ну вообще да, наверное. Но я еще к тому, что и в текущей реализации перемещения «обращение к moved out» переменной — это не ужас-ужас, а вполне легальная и осмысленная операция, если все сделать аккуратно. Не знаю, можно ли корректно и удобно надстроить «настоящий» destructible move поверх текущей реализации, но если «просто» задепрекейтить обращение в переменной после std::move, то это, боюсь, сломает много кода. Да и в std:move можно передать не только переменную, а, скажем, тот же элемент вектора, и что тогда делать?
                                  0
                                  «обращение к moved out» переменной — это не ужас-ужас, а вполне легальная и осмысленная операция, если все сделать аккуратно

                                  Это не столько обращение, сколько повторная инициализация.


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

                        +2
                        если ты перелил чай из чашки в желудок, то это не означает, что чашку надо немедленно уничтожить. Зачастую гораздо дешевле просто налить в нее новый чай.

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

                          +1
                          — Доктор, когда я делаю вот так, у меня тут болит!
                          — А вы так не делайте.
                            +1

                            Ну вот компилятор раста и говорит, что так делать не надо)


                            Видите, у нас с вами полное взаимопонимание!

                              –2
                              Компилятор раста много чего говорит. А потом тот же автор actix'а вынужден его затыкать потому, что компилятор говорит ерунду.
                                +2

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

                                  0
                                  Ну, на той системе типов, которой располагает раст, ему, видимо, не удалось сделать то, что ему нужно, с той эффективностью, которую он хотел получить. Так что про «необходимость» — это как посмотреть.
                                    +2

                                    Ну да. Костыльками сделал быстрее, чем любое существующее решение на С++. Дайте время, сделает красиво и без костылей.

                                      0
                                      При этом написав в «стиле C», просто на расте, за что его, собственно, и гнобят (я, кстати, не гноблю — я понимаю необходимость компромиссов, и что иногда приходится делать «не так, как советуют лучшие практики»).
                                        0
                                        написав в «стиле C»

                                        15 раз на проект в десятки тысяч строк кода? Вот уж действительно преступление!
                                        Почему вы в обратную сторону не считаете? Он написал 50к строк кода в стиле Раст и получил производительность лучше, чем все существующие решения?)) Уже не так хорошо звучит для вас, как хотелось бы, да, мистер хейтер?)

                                          –2
                                          «Преступление», которое ему вменяют ревнители духа раста, как раз и состоит в том, что он по сути обманом отключил механику borrow checker'а, и обеспечивал то, что одновременно будет всего одна мутабельная ссылка на один объект, вручную. Я как раз не считаю это таким уж преступлением, но компилятор уже не мог проверить, что мутабельная ссылка, возвращавшаяся рядом вспомогательных функций — действительно единственная. Я понимаю, что частенько программисту приходится просто доверять, если хочешь получить результат «выше среднего». Фанатики борроу чекера — нет.
                                            0
                                            Фанатики борроу чекера — нет.

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


                                            если хочешь получить результат «выше среднего».

                                            Я еще помню времена, когда в проекте было 150 unsafe. Ну убрали 90% unsafe, проекту от этого плохо не стало, как был на первом месте, так и остался (а не выше среднего). Уберут и оставшиеся, так вы другой проект начнете приводить в пример, как "невозможно писать быстрый раст код без unsafe". Фанатичность она такая, да.

                                              0
                                              «Убирание» unsafe там заключалось главным образом в том, что убирался unsafe в описании и в местах вызова функций, написанных в подобном стиле:

                                              pub(crate) fn get_mut(&mut; self) -> &mut; T {
                                                  unsafe { &mut; *self.inner.as_ref().get() }
                                              }
                                              

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

                                                Нет, нет, нет, нет, и нет. Лень все пруфы искать.


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

                                                  0
                                                  Ну как же «нет», если по первой же вашей ссылке, насколько я понимаю, примерно это и происходит. Что делает эта as_mut()? Она случайно не возвращала сначала «голый» указатель, и была объявлена как unsafe, а потом переписана в стиле, подобном вышеприведенному?
                                                    +1
                                                    Хотя да, вроде бы ничего такого там нет, я по крайней мере не нашел. as_mut() — это там из Option. Ну что ж, пока, насколько я понимаю, конструкции типа вышеприведенной предлагают убрать с помощью RefCell, против чего автор сопротивлялся, небезосновательно предполагая, что это отрицательно скажется на производительности. Будем посмотреть.
                                                      0

                                                      Option::as_mut никогда не возвращала голый указатель. Там проблема в ненужном трюкачестве в виде каста ссылки к указателю as *mut _, а потом обратно.

                                  +2
                                  Ну вот компилятор раста и говорит, что так делать не надо)
                                  Не говорит. Нет ничего плохого в наполнении пустой (или даже потенциально пустой, с выливанием старого чая, если нужно) чашки новым чаем — в том числе с точки зрения компилятора Rust.
                                    0

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


                                    let stomach = cup;
                                    let stomach2 = cup;

                                    Вот так не даст скомпилировать, если cup !Copy.

                                      0
                                      Именно что с левой. Речь шла не о том, чтобы пить из пустой чашки, а о том, чтобы
                                      налить в нее новый чай

                                      После чего её, конечно, можно будет и справа использовать.
                                        0
                                        Точнее, просто изначальная аналогия неудачная (не иллюстрирует разницу реализации перемещения в Rust и C++). «Наливать новый чай» позволяют оба языка.

                                        Ну а «пить из пустой чашки» действительно никому не нужно. А если всё же нужно, используют Option или std::mem::take.
                        +6
                        там голые структуры взятые из llvm(читай си).
                        Не надо фантазировать. В расте полноценные пользовательские типы данных с инкапсуляцией состояния, методами и деструкторами. Единственное, что их роднит с «голыми структурами (читай си)» — тривиальная перемещаемость (от которой, если очень нужно, можно уйти, спрятав объект за Pin-ом). И слово struct, которым (наряду с enum) они объявляются.
                        Т.е. нет никакие конструкторов, нету наследования, прав доступа, инициализаторов и прочего.
                        Права доступа есть. doc.rust-lang.org/reference/visibility-and-privacy.html

                        Наследования нет, но есть реализация интерфейсов (trait-ов) и композиция, которые решают те же задачи более изящно.

                        Конструкторов нет, поскольку в отсутствие традиционного наследования задача инициализации объекта корректнее и гибче решается функцией (без параметра self), возвращающей объект. В такой функции можно вычислить значения всех полей до создания объекта, что полностью исключает саму фазу инициализации (в течение которой объект находится в нецелостном состоянии, но виден конструктором и вызываемыми из него методами), а также из неё можно вернуть нечто более сложное, чем просто инициализированный объект (например, монаду вроде Result или Option).
                          –13
                          Не надо фантазировать. В расте полноценные пользовательские типы данных с инкапсуляцией состояния, методами и деструкторами.

                          Нет, очевидно. Там голые структуры из llvm(читай си). Никаких методов и деструкторов нет. К тому же, заметим, что я не говорил ничего про деструкторы, но адепт уже начал про них что-то рассказывать? Почему? Правильно — он не читал, не думал — он просто ретранслирует пропаганаду.


                          Есть только drop, который не деструктор. Это что-то типа finalize в жава. Раст это изначально скриптуха с ГЦ, к которой просто накрутили костылей.


                          Никаких методов нет — есть просто отдельные функции. Перегрузки нет, поэтому взятая из C++-перегрузка превращена в трейты, которые по-сути являются костылями/сахором поверх перегрузки.


                          Никаких прав доступа, наследования и прочего — нет, очевидно. Инициализаторов полей нет, конструкторов нет. Есть просто списки инициализаций из си.


                          Единственное, что их роднит с «голыми структурами (читай си)» — тривиальная перемещаемость

                          Роднит их то, что это это и есть структуры си. А тривиально перемещаются они так же, как подобные структуры в C/С++.


                          (от которой, если очень нужно, можно уйти, спрятав объект за Pin-ом).

                          Это уже костыль. В связи с тем, что объекты в расте беспорядочно копируются, потому как по природе это скриптуха с ГЦ. Все типы ссылочные, переменные не являются стореджем как в С/С++.


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


                          И слово struct, которым (наряду с enum) они объявляются.

                          Опять же, не только.


                          Права доступа есть.

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


                          Наследования нет, но есть реализация интерфейсов (trait-ов) и композиция, которые решают те же задачи более изящно.

                          Опять же, чушь. Ничего они не решают. Ненужно в ответ мне ретранслировать пропаганду локальную — она мне не интересна.


                          Трейты — это нахлабучка поверх перегрузки. Она никак не влияет на структуру.


                          Конструкторов нет, поскольку в отсутствие традиционного наследования задача инициализации объекта корректнее

                          Опять пошла пропаганда второсортная. Ещё раз, меня не волнуют сектантские лозунги и за аргументацию они не считаются.


                          Дополнительные возможности — это следствие. Основное свойство конструктора — это создать класс функций, которые именно создают объекты. А дополнительные возможности — это вторично.


                          и гибче решается функцией (без параметра self), возвращающей объект.

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


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


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


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


                          В такой функции можно вычислить значения всех полей до создания объекта

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


                          Всё это ничего не значит. Тут адепты немного перепутали реальность. Пропаганда им внушает, что их реалии — это что-то новое, но нет. Это 70 год. Самый примитив. Поэтому, если каким-то чудом я стану адептом этих лозунгов — я всегда могу использовать статические методы.


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

                          Мусорные лозунги. Это было в 70 году в си. Это ничего не значит. Смысл инициализации именно в том, что конструктор и есть атомарный инициализатор.


                          а также из неё можно вернуть нечто более сложное, чем просто инициализированный объект (например, монаду вроде Result или Option).

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


                          К тому же, от создания объекта — мне нужен объект, а не мусор. К тому же, зачем мне перечислять примитивную чушь? Я понимаю, что пропаганда убедила адептов, что это какие-то новшества, но нет. Это то, что существовало ещё до рождения данного адепта.

                            +8
                            Никаких методов и деструкторов нет.
                            Никаких методов нет — есть просто отдельные функции.
                            Есть только drop, который не деструктор. Это что-то типа finalize в жава. Раст это изначально скриптуха с ГЦ
                            Все типы ссылочные, переменные не являются стореджем как в С/С++.
                            Но в основном они нужны потому, что в данной скриптухи нет перегрузки. В жаве так же, поэтому там так же используются статический методы для не-стандартных инициализаций.

                            Методы (не просто отдельные функции) есть.

                            Деструкторы есть. drop — это именно что детерминированно вызываемый деструктор, после которого столь же детерминированно вызываются деструкторы полей, а никак не «что-то типа finalize в жава».

                            ГЦ нет. (Давным-давно был, удалили в 2014-м, ещё до выхода первой стабильной версии Rust.) И никаким боком раст не подходит под определение «скриптухи» (ни по достоинствам, ни по недостаткам).

                            Типы НЕ ссылочные: переменные являются стореджем, как в C++.

                            В жаве перегрузка есть, в том числе для конструкторов, статические методы для создания объектов там используются по совсем другим причинам (например, для возможности создания объекта производного класса — погуглите «фабричный метод»).

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

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


                              Методы (не просто отдельные функции) есть.

                              Нету.


                              Деструкторы есть. drop — это именно что детерминированно вызываемый деструктор, после которого столь же детерминированно вызываются деструкторы полей, а никак не «что-то типа finalize в жава».

                              Опять какая-то чушь. Зачем адепт ретранслирует нелепые тезисы пропаганды? Деструктор — это то, что привязано к времени жизни объекта. drop никакого отношения к этому не имеет. Это именно finalize из жавы.


                              Вся эта чушь про какой-то рекурсивный вызов для полей — это просто чушь. Она ничего не значит. И никакого отношения к теме не имеет.


                              ГЦ нет. (Давным-давно был, удалили в 2014-м, ещё до выхода первой стабильной версии Rust.)

                              Подождите, но неужели адепт опять врёт? Что же данный адепт невежества цитирует "раст изначально скриптуха с ГЦ", а потом мне отвечает, что ГЦ то был, но сплыл. Т.е. я прав, ведь я не сказал, что он сейчас с ГЦ.


                              Но это неважно, потому что даже сейчас эта скриптуха с ГЦ. Семантически там везде ГЦ. Просто адепты этой скриптухи не особо компетентны в вопросе, но я им помогу.


                              Зачем вообще нужен ГЦ? Ой, основная его задача — перекрёстные ссылки. Без этого достаточно и рефкаунта. Но постойте, но ведь раст-скриптуха не может в перекрёстные ссылки. Ой, а почему же?


                              Таким образом, данное ограничение в скриптухе просто позволяет выкинуть ГЦ и заменить его на рефкаунт. Но ведь пропаганда говорит адептам, что там нет ГЦ? На самом деле она врёт. Там есть ГЦ на rc, но с некоторыми фокусами.


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


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


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


                              Схема там простая как топор. В этой примитивной скриптухи есть только ссылки и агрегаты. Со ссылками проблем особо нет. Есть проблемы с агрегатами.


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


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


                              Соответственно, сам анализ простой как топор. Есть верхний объект — он создаётся. Далее все ссылки — ссылки. Там неважно копируются они или не копируются. Есть ли у них сторедж, либо нет. Это просто ссылки.


                              Соответственно, если в каком-то из блоков существует более двух ссылок — ссылки перестают быть мутабельными. Таким образом к ним попросту невозможно применить drop.


                              Далее, в том блоке(скоупе), где объявлен алиас/ссылка и если она одна — то по выходу из скоупа скриптуха вставляет там drop. Всё.


                              Т.е. это такая вариация rc статическая. Далее всё остальное — это unsafe-хаки. Т.е. рантайм.


                              И никаким боком раст не подходит под определение «скриптухи» (ни по достоинствам, ни по недостаткам).

                              Подходит. Всем.


                              Типы НЕ ссылочные: переменные являются стореджем, как в C++.

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


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

                              Нет. То, что там есть — это мусор. Оно не применимо r описываемой мною функциональности.


                              статические методы для создания объектов там используются по совсем другим причинам (например, для возможности создания объекта производного класса — погуглите «фабричный метод»).

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


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


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

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


                              Т.е. ноль равный нулю попытался что-то там поотвечать, но ничего не смог. Это норма для адептов данного учения.

                                +8
                                Ой, адепт просто погулил private и кинул первую ссылку в гугле, но опозрился. Ой, а потом просто забил на ответ сославшись на то, что это враньё?
                                Ой, простите, при составлении списка наглой лжи я действительно совсем забыл про этот пункт.
                                Итак, вы писали:
                                Это костыли из модулей, о которых я ничего не говорил. Я говорил про права доступа для полей.
                                и, ещё раньше:
                                Кто угодно имеет полный доступ к объекту, как это было всегда в си.
                                Отвечаю: вот наиболее релевантный фрагмент моей «первой ссылки из гугла», опровергающий ваш тезис про всеобщий доступ к объекту за счёт приватного права доступа для поля:
                                // Declare a public struct with a private field
                                pub struct Bar {
                                    field: i32,
                                }

                                Все типы ссылочные
                                про ссылочные типы я ничего не говорил.
                                Занавес.
                      +1

                      Вот как раз copy method взять не получится, потому что обратная совместимость. Но это всего лишь принятое соглашение по умолчанию, оно ни на что принципиально не влияет.


                      А вот "уничтожать" объекты после перемещения можно попробовать. Использование после перемещения и сейчас считается дурным тоном, так что много кода затронуто не будет. Скажем, в C++23 можно объявить использование после перемещения deprecated, а в C++29 или C++32 сделать ошибкой. Если язык до тех пор доживёт.

                        0

                        Copy method как раз несложно внедрить для нового кода. Да и для старого можно, пометив использование копирующих конструкторов как deprecated.
                        А вот разрушающее перемещение сломает слишком многое. Я уже выше писал, что семантика "владения" в С++ натянута поверх C PODs.
                        По поводу доживёт или нет — вопрос слишком холиварный. Моё мнение — будет как минимум занимать значимую часть рынка, а то и останется доминирующим. Легаси, база наработанных знаний, рынок спецов, привычки в конце концов.

                          –1
                          Использование после перемещения и сейчас считается дурным тоном.

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

                        +1
                        Я как-то обхожусь без move-семантики, обычными указателями и ссылками на объекты. Как мне кажется, это делает код более прозрачным — явно видно что передается не объект (по значению) а его адрес (по ссылке).
                        Я помню времена, когда не было ни лямбда-функций, ни move-семантики. И при написании кода достаточно часто и весьма остро чувствовалось, что вот именно лямбд ну очень не хватает. А потребностей в move-семантике как-то не возникало вообще…
                        Это я отстал от жизни, или действительно могут быть разные подходы к архитектуре?
                        И еще вопрос — в каких языках кроме С++ есть move-семантика? В чем отличия и особенности?
                          +3
                          И еще вопрос — в каких языках кроме С++ есть move-семантика? В чем отличия и особенности?

                          В Rust есть. Там по-умолчанию move, но это подается под соусом "передачи владения"; владение можно либо отдать на совсем (move), тогда объектом больше пользоваться нельзя и деструктор для него не вызовется, либо объект можно одолжить (borrow), тогда заимствующий не может его удалить. И скопировать можно.


                          Ну так, на пальцах. Это очень логично ложится на обычную логику владения ресурсами (памятью, доступом к i/o и т.п.), которая в других языках выражается неявно.

                            0
                            А когда эти два примера обоснованы для использования? В голову идет только разве что переход из одного хранилища в другое (игрок А передает предмет Б игроку Ц)
                              +3

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


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


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

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

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

                                  +1

                                  Это отрадно слышать.

                                    0
                                    Имхо спорно. На моей практике проще за три дня написать код на C++, написать для него тесты, отладить и исправить ошибку, чем неделю воевать с компилятором Rust за право успешно собрать код.
                                      +3

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


                                      У меня на практике была отладка самописного асинхронного фреймворка на boost::asio, вот я четыре месяца отлавливал баги в чужом многопоточном коде. Врагу не пожелаю такой жопы. И никогда таких ошибок я не встречу в расте.

                                        0

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

                                          +1
                                          Я считаю, что в большинстве случаев на C++ можно написать понятный, надёжный и производительный код, не тратя какие-то большие ресурсы. Проблемы могут начаться только в случае, когда идёт война за байты и такты.
                                          То есть понятно, что раст многие проблемы решает за счёт своего устройства, но при этом сильно теряется гибкость в плане безопасности. Тут, кстати, пример красочный проскочил: habr.com/ru/post/484436 Получается, что у пользователей раста нет возможности скрыть какой-то небезопасный участок кода внутри проекта, чтобы не мучиться с его вылизыванием.
                                            0

                                            У меня закрадывается стойкое подозрение, что Вы на пару с KanuTaH не разобрались или специально искажаете факты. Потому что смысл unsafe ведь не в том, что он "заражает" все остальное собой, а в том, что мы можем внутри него делать как бы что угодно, при условии, что возвращаясь в "обычный" код мы "не нарушаем инварианты языка" (цитата из каких-то комментов, лучше я сам не объясню). Другой вопрос, что с actix'ом пример вышел какой-то совсем неудачный. И из того краткого поиска, что я провел, есть более 'безопасные' и 'идиоматичные' реализации того же самого.
                                            Ну, и, да, быстрый код — не значит корректный (это очевидно). Сам грешил таким, когда был студентом и мы сдавали программы в тестовую среду, которая гоняла их по заранее определенным (не неизвестным для участников олимпиады) наборам тестов

                                              0
                                              Я нигде и не писал, что он непременно «заражает» все остальное собой. Но вот этот код что я привел выше — он определенно unsound. По канонам раста все-таки нельзя «просто так брать и» возвращать мутабельную ссылку, сделанную из голого указателя, не убедившись в том, что такая ссылка действительно будет в один момент времени только одна.
                                                0

                                                Я с этим и не спорю. И почти наверняка такой код будет пахнуть и в с++ потенциальными проблемами.

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

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

                                                      0

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

                                                0
                                                Честно говоря, ни разу не понял как ваш комментарий относится к моему.
                                                  0
                                                  Потому что смысл unsafe ведь не в том, что он «заражает» все остальное собой, а в том, что мы можем внутри него делать как бы что угодно, при условии, что возвращаясь в «обычный» код мы «не нарушаем инварианты языка»
                                                  смотря в каком смысле «заражает». Если в foo() возможна ошибка, то и bar(), вызывающий foo(), может работать некорректно, верно? Никто ведь не гарантирует что программист не допустил ошибку в unsafe блоке или иным способом не нарушил инварианты, на которые он рассчитывал в этом unsafe блоке.
                                                  +5
                                                  Я считаю, что в большинстве случаев на C++ можно написать понятный, надёжный и производительный код, не тратя какие-то большие ресурсы.

                                                  Вот тут я с вами не согласен.


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

                                                    0
                                                    Объяснитесь, сударь!
                                                      +4

                                                      Мин много, всё время под ноги смотреть надо.


                                                      Хочется прочитать данные из сокета в структуру? Не, reinterpret_cast нельзя, надо memcpy. Хочется прочитать быстро? Не, всё равно нельзя, надо надеяться на компилятор.


                                                      Хочется сделать POD, который сам синхронизируется с диском? Ну, через mmap чтобы. Инициализировать объект? Танцы с memcpy и placement new.


                                                      Хочется написать свой полувектор-полудек для POD'ов с некоей хитрой стратегией роста? Надо помнить о лайфтаймах объектов даже в случае POD'ов. Хорошо хоть, что с проблемами от адресации к массивам вопросов в данном конкретном случае не возникает.


                                                      Хочется сравнить две структуры эффективно? Не, operator== пока ещё не генерируется сам, а memcpy нельзя (но нельзя так, что это может работать в дебаговой сборке, а в релизной работать не будет). Давай, не ленись, страдай, пиши оператор сравнения ручками и надейся, что компилятор его соптимизирует в случае отсутствия паддинга.


                                                      Пишешь сложный алгоритм с вложенными циклами? Не смей даже формировать указатель за пределы одного из массивов. А если сформировал твой коллега — попробуй договориться с тимлидом о том, надо ли писать такой код или не надо, и какая вообще политика по UB.


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


                                                      template<typename T>
                                                      void foo(std::optional<T> a)
                                                      {
                                                          std::optional b { a };     // какой конструктор тут вызовется?
                                                      }

                                                      К слову о конструкторах. В каких из этих строк UB? Это зависит от версии C++?


                                                      struct Foo1
                                                      {
                                                          int a;
                                                      };
                                                      
                                                      struct Foo2
                                                      {
                                                          int a;
                                                          Foo2() = default;
                                                      };
                                                      
                                                      struct Foo3
                                                      {
                                                          int a;
                                                      
                                                          Foo3();
                                                      };
                                                      
                                                      Foo3::Foo3() = default;
                                                      
                                                      int main()
                                                      {
                                                          Foo1 foo11, foo12 {};
                                                          Foo2 foo21, foo22 {};
                                                          Foo3 foo31, foo32 {};
                                                      
                                                          std::cout << foo11.a << std::endl;
                                                          std::cout << foo12.a << std::endl;
                                                          std::cout << foo21.a << std::endl;
                                                          std::cout << foo22.a << std::endl;
                                                          std::cout << foo31.a << std::endl;
                                                          std::cout << foo32.a << std::endl;
                                                      }

                                                      Как насчёт такого кода? Можно показывать как ржаку на вечеринках, ящетаю.


                                                      struct Foo
                                                      {
                                                          int a;
                                                      
                                                          Foo() = delete;
                                                          Foo(int) = delete;
                                                      };
                                                      
                                                      int main()
                                                      {
                                                          Foo foo1 {};
                                                          Foo foo2 { 10 };
                                                      }

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

                                                        –1
                                                        Хочется сравнить две структуры эффективно? Не, operator== пока ещё не генерируется сам, а memcpy нельзя (но нельзя так, что это может работать в дебаговой сборке, а в релизной работать не будет). Давай, не ленись, страдай, пиши оператор сравнения ручками и надейся, что компилятор его соптимизирует в случае отсутствия паддинга.

                                                        В смысле?

                                                        В конце концов, можно ли взять свежих выпускников вузов

                                                        Свежих выпускников вузов вообще куда-то бросать опасно. Даже если они будут делать веб-страничку, они обязательно забудут про какой-нибудь rel=«noopener». Программирование — сложная штука, как ни крути. Везде много нюансов.
                                                          +1
                                                          В смысле?

                                                          У вас там C++2a и trunk-компилятор. Извините, я не собираю код в прод транковыми компиляторами и ещё невышедшими стандартами.


                                                          Кстати, интересно, что для


                                                          struct alignas(2) S
                                                          {
                                                              char a = 0,
                                                                   b = 0;
                                                          
                                                              bool operator==(const S &rhs) const = default;
                                                          };

                                                          компилятор всё равно сравнивает структуру поэлементно, а мог бы сразу целиком оба члена данных:


                                                                  movzx   edx, BYTE PTR [rsi]
                                                                  xor     eax, eax
                                                                  cmp     BYTE PTR [rdi], dl
                                                                  jne     .L1
                                                                  movzx   eax, BYTE PTR [rsi+1]
                                                                  cmp     BYTE PTR [rdi+1], al
                                                                  sete    al
                                                          .L1:
                                                                  ret

                                                          Свежих выпускников вузов вообще куда-то бросать опасно. Даже если они будут делать веб-страничку, они обязательно забудут про какой-нибудь rel=«noopener». Программирование — сложная штука, как ни крути. Везде много нюансов.

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

                                                            0

                                                            У меня там — O0 стоит, может, поэтому. Да, это пока в транке, возможность иметь default operator == появилась одновременно с operator spaceship. Но процесс-то идёт!

                                                              +1
                                                              У меня там — O0 стоит, может, поэтому.

                                                              Ну вы обижаете :) Я поменял на -O3, конечно.


                                                              Да, это пока в транке, возможность иметь default operator == появилась одновременно с operator spaceship. Но процесс-то идёт!

                                                              Поэтому я написал «пока ещё».

                                                                +2

                                                                Поигрался с этим ещё. Боже, какой трэш.


                                                                Во-первых, можете сказать сразу, без взгляда в стандарт или пропозал, какова должна быть семантика сгенерённого оператора для такой структуры?


                                                                struct alignas(64) S
                                                                {
                                                                    char a[32];
                                                                
                                                                    bool operator==(const S &rhs) const = default;
                                                                };

                                                                gcc и clang Тут несогласны. gcc сравнивает указатели:


                                                                        cmp     rsi, rdi
                                                                        sete    al
                                                                        ret

                                                                clang сравнивает содержимое массивов. Но как он это делает! По байтику! Под кат не кладу, процессор страдает, так что и вы страдайте:


                                                                        mov     al, byte ptr [rdi]
                                                                        cmp     al, byte ptr [rsi]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 1]
                                                                        cmp     al, byte ptr [rsi + 1]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 2]
                                                                        cmp     al, byte ptr [rsi + 2]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 3]
                                                                        cmp     al, byte ptr [rsi + 3]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 4]
                                                                        cmp     al, byte ptr [rsi + 4]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 5]
                                                                        cmp     al, byte ptr [rsi + 5]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 6]
                                                                        cmp     al, byte ptr [rsi + 6]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 7]
                                                                        cmp     al, byte ptr [rsi + 7]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 8]
                                                                        cmp     al, byte ptr [rsi + 8]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 9]
                                                                        cmp     al, byte ptr [rsi + 9]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 10]
                                                                        cmp     al, byte ptr [rsi + 10]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 11]
                                                                        cmp     al, byte ptr [rsi + 11]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 12]
                                                                        cmp     al, byte ptr [rsi + 12]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 13]
                                                                        cmp     al, byte ptr [rsi + 13]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 14]
                                                                        cmp     al, byte ptr [rsi + 14]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 15]
                                                                        cmp     al, byte ptr [rsi + 15]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 16]
                                                                        cmp     al, byte ptr [rsi + 16]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 17]
                                                                        cmp     al, byte ptr [rsi + 17]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 18]
                                                                        cmp     al, byte ptr [rsi + 18]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 19]
                                                                        cmp     al, byte ptr [rsi + 19]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 20]
                                                                        cmp     al, byte ptr [rsi + 20]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 21]
                                                                        cmp     al, byte ptr [rsi + 21]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 22]
                                                                        cmp     al, byte ptr [rsi + 22]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 23]
                                                                        cmp     al, byte ptr [rsi + 23]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 24]
                                                                        cmp     al, byte ptr [rsi + 24]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 25]
                                                                        cmp     al, byte ptr [rsi + 25]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 26]
                                                                        cmp     al, byte ptr [rsi + 26]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 27]
                                                                        cmp     al, byte ptr [rsi + 27]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 28]
                                                                        cmp     al, byte ptr [rsi + 28]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 29]
                                                                        cmp     al, byte ptr [rsi + 29]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 30]
                                                                        cmp     al, byte ptr [rsi + 30]
                                                                        jne     .LBB1_32
                                                                        mov     al, byte ptr [rdi + 31]
                                                                        cmp     al, byte ptr [rsi + 31]
                                                                        sete    al
                                                                        ret
                                                                .LBB1_32:
                                                                        xor     eax, eax
                                                                        ret

                                                                alignas тут вообще не роляет, что он есть, что его нет, всё равно по байтику будем брать. SSE-регистры? AVX2 с 256-битными регистрами, чтобы за один лоад/сравнение всё сделать? Да нафиг это надо, фигачь так!


                                                                Надо ещё поиграться. Правда, на локальной машине для бенчмарков компилять gcc из транка неохота.


                                                                И правда, почему это я не юзаю транковые версии компиляторов и стандартов для прода

                                                                  –1
                                                                  К релизу, думаю, такие вещи поправят.
                                                                    0

                                                                    Очевидно что в gcc, что в clang-е кодогенератор для defaulted operator == просто рекурсивно обходит подобъекты, я бы тоже так сделал в первом приближении. При этом ни там ни там случай массовов пока не рассмотрели отдельно. Вы завели issue? Уверен их быстро исправят. Что касается вашего первого примера с char a, b; то как бы вы написали его руками эффективнее?

                                                                      +1
                                                                      Очевидно что в gcc, что в clang-е кодогенератор для defaulted operator == просто рекурсивно обходит подобъекты, я бы тоже так сделал в первом приближении.

                                                                      Нет, в гцц он сравнивает указатели на массивы, в clang он сравнивает сами массивы. Это прям из ассемблера видно.


                                                                      Что касается вашего первого примера с char a, b; то как бы вы написали его руками эффективнее?

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


                                                                      Собственно, я потом ещё с clang'ом поигрался — он что-то такое при достаточно большом alignas и делает (молодец, без сарказма), на -march=native вообще генеря какую-то магию с AVX512 и масочными регистрами (так я про них и узнал вчера впервые, лол).

                                                                        0
                                                                        Нет, в гцц он сравнивает указатели на массивы, в clang он сравнивает сами массивы.

                                                                        Это и получится, если при обходе подобъектов не разобрать случай массивов, там уж как if-ы сложатся, декэим до указателей или идем перебирать элементы.


                                                                        Загружал бы два байта сразу одной командой и сравнивал бы два байта сразу одной командой.

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


                                                                        struct S1 { char c[2]; };
                                                                        struct S2 { array<char, 2> d; };
                                                                        struct S3 { uint16_t e; };
                                                                        struct S4 {  // возможно, в C++23
                                                                          struct layoutas(char[2]) {
                                                                            char a, b;
                                                                          };
                                                                        };

                                                                        Что же до примера с полем char a[32]; замените тип на array<char, 32> и будет то что вы хотите: https://godbolt.org/z/x9Kebq

                                                                          +3
                                                                          Вы говорите про ассемблер, очевидно, что в C++17 это не выразимо для ровно той структуры, что вы написали.

                                                                          Ну, компилятор-то генерирует не C++17-код.

                                                              0
                                                              У нас с Вами, видимо, разное понимание «большинства случаев». Я профессионально пишу на плюсах 4 года (не бог весть что, но всё-таки), и ни разу мне не пришлось столкнуться с вашими примерами. Что я делаю не так?
                                                              Ваши выкладки прямо конспект типичного собеседования: выстрелил себе в ногу, чтобы похвастаться навыками оказания первой помощи.

                                                              какой конструктор тут вызовется?

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

                                                              Хочется прочитать данные из сокета в структуру?

                                                              У вас бинарный протокол? Есть куча либ для сериализации.

                                                              Хочется написать свой полувектор-полудек для POD'ов с некоей хитрой стратегией роста?

                                                              Типичная задача, каждый день что-то такое пишу.

                                                              Надо помнить о лайфтаймах объектов даже в случае POD'ов.

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

                                                              Хочется сравнить две структуры эффективно?

                                                              Сравните неэффективно.

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

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

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

                                                              Смотря что считать производительностью: если вам нужно быстро набросать говнокод, берите питонистов, а если нужно, чтобы продукт не тормозил, то берите плюсовиков. Если у вас не куча легаси, а ребята смышлёные, то через пол года они станут вполне самостоятельными.
                                                                +2
                                                                Я профессионально пишу на плюсах 4 года (не бог весть что, но всё-таки), и ни разу мне не пришлось столкнуться с вашими примерами. Что я делаю не так?

                                                                Ну фиг знает. Я профессионально пишу… ну, что считать профессиональным написанием. Деньги мне платят за код на плюсах последние лет 13 (если кое-какие потуги из старших классов школы считать), свой первый файл с расширением .cpp я создал лет 16-17 назад.


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

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


                                                                template<typename T>
                                                                struct Base {};
                                                                
                                                                template<typename T>
                                                                struct Derived : Base<Metafun<T>>
                                                                {
                                                                    using B = Base<Metafun<T>>;
                                                                
                                                                    using B::Base;  // что делает эта строка?
                                                                };

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

                                                                Я немножко другой аспект имел в виду, давайте переформулирую вопрос. Вот пусть у вас есть шаблонная функция


                                                                template<typename T>
                                                                void foo(T arg)
                                                                {
                                                                    std::optional maybe_arg { arg };
                                                                }

                                                                Каков тип переменной maybe_arg?


                                                                У вас бинарный протокол? Есть куча либ для сериализации.

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


                                                                Типичная задача, каждый день что-то такое пишу.

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


                                                                Сравните неэффективно.

                                                                А, опять же, смысл тогда от плюсов?


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

                                                                А чем будете? Итераторами? Так там та же проблема.


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

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

                                                                  0
                                                                  или вообще хаскель

                                                                  Не хватает известной картинки с вилли вонкой. Ладно, это я так, не обращайте внимания.
                                                                    +1

                                                                    В 2020 уже немодно на эту тему вилли вонку постить.

                                                                    –1
                                                                    Каков тип переменной maybe_arg?

                                                                    Вы намекаете, что там может быть условный T, T& и T&&? Если да, то это можно проверить на этапе компиляции. Или явно указывать аргумент шаблона. Или не передавать всякое непотребство.

                                                                    А чем будете? Итераторами? Так там та же проблема.

                                                                    Какая проблема? Создать итератор за пределами массива? Зачем? Если уж Вам кровь из носа нужен такой костыль, то используйте индексы целочисленные.
                                                                    Ну а мне вот пришлось, когда нужно было решать задачу эффективно. А если бы не нужно было её решать эффективно, то зачем брать плюсы?

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

                                                                    Не знаю что такое профункторы и линзы, поэтому не впечатлён. Но с хаскелля, конечно, посмеялся.
                                                                      +2
                                                                      Вы намекаете, что там может быть условный T, T& и T&&?

                                                                      Нет, я намекаю на то, что если вы передадите в функцию U, то тип будет std::optional<U>, но только если U не равно std::optional<U'> для некоторого U', то тип будет std::optional<U'>. CTAD клёвое, да.


                                                                      Это несколько затрудняет написание обобщённого-кода.


                                                                      Какая проблема? Создать итератор за пределами массива?

                                                                      Нельзя. UB.


                                                                      Зачем?

                                                                      Алгоритм проще записать.


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

                                                                      Я-то и предложил использовать индексы, но вот не очень опытный в плюсах чувак не понял, в чём проблема, и зачем-то подключил тимлида, который в плюсах тоже не очень опытный, и ему стоило больших трудов доказать, что формировать указатель дальше arr + n для массива из n элементов нельзя.


                                                                      Потому что не совсем эффективно на плюсах всё ещё гораздо эффективнее чем на шарпах или джаве.

                                                                      У вас есть какие-то подтверждения этому? Особенно той части, что «гораздо»?


                                                                      Но с хаскелля, конечно, посмеялся.

                                                                      А что вас позабавило?

                                                                        0
                                                                        зачем-то подключил тимлида, который в плюсах тоже не очень опытный, и ему стоило больших трудов доказать, что формировать указатель дальше arr + n для массива из n элементов нельзя.

                                                                        Ну и что же? Я бы вообще не стал подключать к обсуждению плюсов тимлида-фронтендовика (боже мой, слово-то какое). Или шарповика. Или джависта. Или хаскелиста, пусть даже он и богоподобен.
                                                                          0

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

                                                                          0
                                                                          Это несколько затрудняет написание обобщённого-кода.

                                                                          Не совсем понимаю в чём проблема. Укажите явно аргумент шаблона, и не будет никаких неопределённостей. А приведение типов в вашем примере вполне логично. Во всяком случае мне трудно представить необходимость использования optional<optional >. И опять же при желании это всё проверяется соответствующими функциями.
                                                                          Я-то и предложил использовать индексы, но вот не очень опытный в плюсах чувак не понял, в чём проблема, и зачем-то подключил тимлида, который в плюсах тоже не очень опытный, и ему стоило больших трудов доказать, что формировать указатель дальше arr + n для массива из n элементов нельзя.

                                                                          Ну то есть проблема вообще ни разу не в плюсах. Для какой-то специфической задачи Вы решили пожертвовать надёжностью для краткости, а ваши коллеги в силу незнания добавили Вам проблем.
                                                                          У вас есть какие-то подтверждения этому? Особенно той части, что «гораздо»?

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

                                                                          Далеко не каждый выпускник топового технического ВУЗа сможет хорошо писать код в принципе, а Вы тут рассказываете о каких-то мифических ребятах, который через пол года шпарят на хаскелле как на родном языке. При том, речь шла, вроде как, о типичных бизнес-задачах, что в контексте хаскелля добавляет смеха.
                                                                            +2
                                                                            Не совсем понимаю в чём проблема. Укажите явно аргумент шаблона, и не будет никаких неопределённостей.

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


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

                                                                            В этом нет ничего такого, если семантика внешнего optional отличается от семантики внутреннего optional.


                                                                            В конце концов, представьте себе случай, например, мапы MyMap<UserId, std::optional<UserAddress>>. Тогда опшионалы в std::optional<std::optional<UserAddress>> обозначают соответственно, например, отсутствие юзера в базе и присутствие юзера, но отсутствие его адреса.


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

                                                                            Меньше кода заведомо надёжнее, чем больше кода (при прочих равных). Более читаемый и более естественно выражаемый алгоритм надёжнее менее читаемого.


                                                                            но почему-то когда я запускаю какую-нибудь программу на джаве или питоне, она явно жрёт больше памяти чем нужно и грузит процессор больше чем нужно

                                                                            Запускаю хром на C++ — жрёт память (и на моём ноутбуке тормозит с рендерингом, хотя там от хрома до железа исключительно плюсово-сишный стек). Запускаю DCS на C++ — жрёт память и процессор (и почти однопоточный, и крашится иногда).


                                                                            То, что какая-то программа на каком-то языке жрёт память и тормозит, не означает, что на языке невозможно писать быстрые и эффективные программы.


                                                                            Или вот есть, например, KDevelop — быстрый, шустрый, но так же быстро падает на моих проектах, и есть CLion — больше, медленнее, подлагивает, но не падает. Вторым можно пользоваться хоть как-то, первым — вообще никак.


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

                                                                            А чего так? Там что в базы лазать очень приятно, что жсоны по сети гонять. На прошлой своей работе очень много таких вещей именно на хаскеле и делал.

                                                                              0
                                                                              Ещё одна мина под ногами.

                                                                              А никто не говорит, что мин под ногами нет. Всё программирование — это одно большое минное поле. Но на плюсах можно писать надёжные и эффективные программы. Да, скорость написания обычно ниже, чем в джавах и питонах, что, в принципе, коменсируется лучшей производительностью.
                                                                              Тогда опшионалы в std::optional std::optional UserAddress (кек, хабр не переваривает шаблоны тоже) обозначают соответственно, например, отсутствие юзера в базе и присутствие юзера, но отсутствие его адреса.

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

                                                                              Имхо у Вас явно не прочие равные, если надо держать указатели за пределами массива. Но это всё разговоры ни о чём без примера кода.
                                                                              Запускаю хром на C++

                                                                              А есть для сравнения браузер на питоне?

                                                                              То, что какая-то программа на каком-то языке жрёт память и тормозит, не означает, что на языке невозможно писать быстрые и эффективные программы.

                                                                              Нет, не означает. Но только почему-то не пишут. CLion мне очень нравится, но он как жрал память гигабайтами, так и жрёт, хотя его пилит команда профи надо думать за хорошие деньги. Тот же VS работает обычно гораздо шустрее в среднем, а QtCreator ещё шустрее (во всяком случае так было пару лет назад). Есть прекрасный по возможностям и удобству гиткракен (написанный на питоне), который просто не вывозит крупные проекты, а есть встренный плагин в CLion, который работает шустрее.
                                                                              А чего так?

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

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

                                                                                  +1
                                                                                  Но на плюсах можно писать надёжные и эффективные программы.

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


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

                                                                                  Когда программа 95% времени ждёт IO, это не очень важно.


                                                                                  Тот же VS работает обычно гораздо шустрее в среднем

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


                                                                                  QtCreator ещё шустрее (во всяком случае так было пару лет назад)

                                                                                  Да, он весьма быстр. Только умеет сильно меньше и куда менее юзабельный (хотя я не особо глубоко ковырял — невозможности быстро менять cmake-таргет мне хватило, у меня в проекте таргетов двести, вот в kdevelop вообще отлично было сделано, в clion ещё более-менее сносно, а как в креаторе, идти каждый раз при смене таргета лезть в настройки проекта и вводить ручками имя нового таргета — не, спасибо).


                                                                                  Ну во-первых найдите такое количество гребцов на хаскеле для галер =)

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


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

                                                                                  Тут я бы не согласился.


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

                                                                                  Как к range'ам из C++20 относитесь?


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

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

                                                                                    Учитывая, какое количество всякого софта написано на плюсах, Вы ими пользуетесь так или иначе. А уж ваше к ним доверие (я так полагаю, Вам нужно строгое доказательство корректности) — дело десятое.
                                                                                    Когда программа 95% времени ждёт IO, это не очень важно.

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

                                                                                    Хоспаде, ну Вы же прекрасно представляете какие результаты даст поиск по вакансиям на хаскелле и, к примеру, джаве. Там разница в пару порядков.
                                                                                    Как к range'ам из C++20 относитесь?

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

                                                                                    Видимо, толпа питонистов ещё не распробовала все сладкие плюшки хаскеля.
                                                                                      +2
                                                                                      Хоспаде, ну Вы же прекрасно представляете какие результаты даст поиск по вакансиям на хаскелле и, к примеру, джаве. Там разница в пару порядков.

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


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

                                                                                      Ну, и какова стоимость разработки проектов на с++? Сколько человеко-часов, включая тестирование туда зарыто? Ну, и по чесноку, половина проектов, которые выдаются за плюсовые — как минимум на половину состоят из чистого или не очень Си кода.

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

                                                                                        Какое это имеет значение? Если все работадатели вдруг резко побегут из джавы в хаскель, где они возьмут столько специалистов? И даже если не резко, я уверен, что и так существующая нехватка спецов только усилится.
                                                                                        Ну, и какова стоимость разработки проектов на с++? Сколько человеко-часов, включая тестирование туда зарыто? Ну, и по чесноку, половина проектов, которые выдаются за плюсовые — как минимум на половину состоят из чистого или не очень Си кода.

                                                                                        А это тут при чём? Я что-то потерял нить. Если Вы намекаете, что эффективность использования C++ низка по сравнению с другими языками, то предоставьте какую-то статистику.
                                                                          0
                                                                          А чем будете? Итераторами? Так там та же проблема.
                                                                          у вас std::end для любого массива/вектора выдаст итератор на элемент за его пределами. Удивлю, наверно, но это не UB
                                                                            0

                                                                            Для массива arr длины n выражение arr + n валидно, а arr + k где k > n — нет.

                                                                              0
                                                                              и зачем вам вообще может быть нужен arr + k для k > n?
                                                                                +1

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


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

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

                                                                                  Самое смешное/грустное в том, что в стандарте многие UB на граничных случаях оставлены ради возможных оптимизаций, которые в будущем не появятся, т.к. они могут сломать код с такими UB. Замкнутый круг
                                                                                    +2
                                                                                    знали бы, если бы это стреляло.

                                                                                    То, что стреляет, тоже не знают.


                                                                                    Да и не факт, что это не будет стрелять в будущем. И лично мне проще стараться вообще не допускать UB, чем размышлять, будут ли в будущем компиляторы этим пользоваться или нет. А то могут быть забавные эффекты — вы ведь наверняка слышали, как clang вызывает никогда не вызываемую функцию?

                                                                            –1

                                                                            Явное лучше неявного. В именно этом случае следовало использовать std::make_optional. Ну или хотя бы раз прочитать о CTAD и std::optional из первоисточника (cppreference годится) (мне 1 раза хватило, чтобы понять, что у этого кода есть особенность, на которую вы указываете).

                                                                          0
                                                                          В конце концов, можно ли взять свежих выпускников вузов и бросить их на плюсовый проект, надеясь, что они там будут так же производительны, как на каком-нибудь пусть даже жс или питоне? Думаю, что ответ вы знаете.
                                                                          когда выпускников вузов бросают на проекты с жс'ом или питоном они обычно тоже не шибко производительны. Ну будут у вас падения не по SIGSEGV, а по «AttributeError: 'NoneType' object has no attribute 'foo'». Программа ведь всё еще не работает
                                                                            +4

                                                                            Падение по SIGSEGV — лучший случай (и то не всегда). Я благодарю богов C++ каждый раз, когда программа падает по SIGSEGV, а не случайно портит какие-то из данных, например. И, опять же, то не всегда — причина крэша может быть сильно в другом месте что в пространстве, что по времени. Или крэш вообще может начать проявляться только при определённом уровне оптимизации, при определённом контексте использования функции, или же через 5 лет при апгрейде компилятора.


                                                                            В каком-нибудь питоне такой ерунды не будет. Ограничивать проблемы в плюсах SIGSEGV'ами и сравнивать их с трейсбеками в питоне, при всей моей любви к плюсам и нелюбви к питону — не стоит.

                                                                        0
                                                                        На всякий, C++ — тот язык, на котором я пишу дольше всего.
                                                                        но в последние годы (когда его здорово причесали) вы на нем не пишете, я правильно понимаю?
                                                                          +1

                                                                          А на что это влияет? Понятности там и правда стало больше. А вот "бесплатной" с точки зрения производительности надёжности не прибавилось.

                                                                            0
                                                                            Ну как же не прибавилось то: раньше чтобы передавать владение объектом его выделяли в куче и прокидывали в виде указателя, а сейчас тот же объект можно выделить на стеке и мувить — в итоге экономятся аллокация/деаллокация, не надо заботиться о «забывании» старого указателя и удалении объекта. Кое-где в новом с++ даже мувить не приходится, например благодаря std::set::emplace. По мне так и быстрее и безопаснее.
                                                                              +1

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


                                                                              А что насчёт ситуации, когда одна функция владеет объектом, а ещё несколько заимствуют его? Быстрый способ тут — передавать ссылку на объект, надёжный — передавать weak_ptr.

                                                                                –1
                                                                                мувить не сам объект, а unique_ptr на него.
                                                                                ну сделайте unique_ptr на объект и мувьте его, какие проблемы то?
                                                                                А что насчёт ситуации, когда одна функция владеет объектом, а ещё несколько заимствуют его?
                                                                                define «владеет» и «заимствует» в этом контексте. А еще лучше с примером. Сейчас я не могу трактовать это сообщение иначе как откровенный бред
                                                                                  +1

                                                                                  «Владеет» = управляет временем жизни.
                                                                                  «Заимствует» = имеет доступ, но не управляет временем жизни.

                                                                                    0
                                                                                    «Владеет» = управляет временем жизни.
                                                                                    передаем по rvalue/unique_ptr
                                                                                    «Заимствует» = имеет доступ, но не управляет временем жизни.
                                                                                    передаем по обычной ссылке

                                                                                    зачем использовать weak_ptr в однопоточке?
                                                                                      0

                                                                                      А как вы гарантируете, что обычная ссылка не переживёт объект?

                                                                                        0
                                                                                        bar(const SomeObject& o);
                                                                                        
                                                                                        void foo() {
                                                                                            SomeObject o; // создан здесь
                                                                                            bar(o); // передали по ссылке
                                                                                            // уничтожен где-то здесь
                                                                                        }
                                                                                        внимание вопрос: как вы себе представляете ситуацию, при которой ссылка не переживает объект?
                                                                                          +2

                                                                                          Вариант раз:


                                                                                          struct baz {
                                                                                              const SomeObject& o;
                                                                                          }

                                                                                          Вариант два:


                                                                                          task<> bar(const  SomeObject& o) {
                                                                                              co_await ...;
                                                                                              // упс, куда теперь указывает o?
                                                                                          }
                                                                                            0
                                                                                            Вариант раз:
                                                                                            первоначальная ссылка всё еще не пережила ссылаемый объект. И обычно программист думает, можно ли сохранять ссылку на временный объект куда-то.
                                                                                            Вариант два:
                                                                                            так ссылка не инвалидируется же
                                                                                              +4
                                                                                              И обычно программист думает, можно ли сохранять ссылку на временный объект куда-то.

                                                                                              Ну так это и называется "небезопасно". Было бы безопасно — об этом бы думал компилятор, а не программист.


                                                                                              так ссылка не инвалидируется же

                                                                                              Что значит — не инвалидируется? Даже вот так она не инвалидируется?


                                                                                              void foo() {
                                                                                                  SomeObject obj;
                                                                                                  start_task(bar(obj));
                                                                                              }
                                                                                                –3
                                                                                                вы действительно хотите играть в игру «язык X так умеет, а язык Y — нет», приводя абсурдные примеры, предполагающие что мидл, написавший костяк кода, допустит тривиальнейшую ошибку?
                                                                                                  +3

                                                                                                  Если ошибка и правда тривиальная — почему компилятор не может её "поймать"?

                                                                                                    0
                                                                                                    false positive ничуть не лучше чем false negative
                                                                                0
                                                                                не надо заботиться о «забывании» старого указателя

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

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

                                                                              от того, что его причесали, С++ проблемой и средством #1 по выстреливанию себе в ногу быть не перестал

                                                                                +4

                                                                                Нет, пишу, к сожалению.

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

                                                                    Естественно есть куча других способов сделать то же самое и этот не всегда оптимален, но это опция.
                                                                  +5
                                                                  Сама по себе move-семантика в повседневном коде не очень часто полезна, но на ее основе можно создать кучу отличных удобных инструментов. Взять тот же unique_ptr. По сравнению с auto_ptr это совершенно другой уровень удобства, понятности, надежности кода. А ведь различия между этой парой — как раз в попытке реализовать в auto_ptr нужную функциональность которую сейчас реализует более разумным способом move. «Умные указатели» делают код намного более читаемым и надежным чем «обычные», а без move это вся красота нормально не работает.
                                                                    0
                                                                    А потребностей в move-семантике как-то не возникало вообще…

                                                                    Попробуйте положить thread, unique_ptr или любой другой некопируемый тип в контейнер вроде vector по значению без move-семантики.

                                                                    0

                                                                    Как по мне, плохо не столько отсутствие move, сколько неявный copy по умолчанию.
                                                                    Не всегда хватает указателя или ссылки, иногда надо сам объект переместить. Файловый дескриптор к примеру.
                                                                    По языкам — наверное только Rust. В D есть RAII в какой-то форме, но насчёт мувов не уверен.

                                                                      +1
                                                                      Я пытаюсь понять, насколько концепция move вообще необходима в языке программирования, является ли она «естественно-первичной» или это следствие затыкания каких-то дыр в других не вполне совершенных концепциях. Тот же С++ известен своими огромными нагромождениями, возникающими из-за обратной совместимости с предыдущими версиями вплоть до Си… Может быть слишком путано сформулировал, но вот как-то так…
                                                                        0
                                                                        Концепция «владения» является настолько «естественно-первичной» что вокруг нее целый язык программирования Rust соорудили :)
                                                                          0
                                                                          Ну вот вы написали, что пользуетесь указателями для передачи объекта. Как я понимаю, один из смыслов move семантики — избавиться от логики указателей.
                                                                          То есть например, добавили вы в std::list элемент. А потом решили, что этот элемент там не нужен, но нужен в каком-то другом месте. Можно сделать это разными способами. Можно сделать std::list<T*>, но тогда придется управлять временем жизни объекта самому. Но есть и другой минус. Здесь возникает такой вопрос, что в std::list объекты уже создаются в куче. Зачем еще один промежуточное представление элемента в куче? То есть получается, что мы сначала создаем элемент T в куче, а потом создаем элемент list::node тоже в куче, который держит только указатель на элемент в тойже куче. При этом list уже содержит элементы в куче, тоесть дополнительной косвенности вроде как и не требуется

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

                                                                        А зачем перемещать локальные переменные? Они же локальны, пускай на своем месте и остаются

                                                                          +2

                                                                          Чтобы меньше задумываться о копировании-перемещении сложных объектов.
                                                                          Создал я T t; и хочу наиболее оптимально по скорости и безопасности вернуть это дело из фунции.
                                                                          Я в курсе про copy-elision/NRVO и прочие штуки.
                                                                          Хочется простоты и порядка.

                                                                          +7

                                                                          На с++ вообще можно просто писать код не держа в голове компилятор? Может есть какие-то бест практисы вроде пишите просто вот так и так и не будет у вас болеть голова. Или все настолько плохо что на каждой строкой кода надо думать 10 минут что она там явно неявно делает?

                                                                            +2

                                                                            Всё довольно плохо. Из-за обратной совместимости, восходящей к Си, синтаксис местами уродлив, новые фичи прибиваются сбоку гвоздями. Зато куча библиотек и производительность. За бест практисами обращайтесь к CppCoreGuidelines.

                                                                              +2
                                                                              В проекте со «старшими товарищами» — можно. Плюсы по натуре своей многоуровневый язык, когда есть по сути возможность написать свои собственные правила и бест практисес и заставить компилятор их проверять. При грамотной организации кода «обычному» программисту в рамках уже созданных кем-то другим библиотек и фреймворков легко можно писать особо не задумываясь а код будет хорошо читаем и адаптирован к задаче. Вот сами эти библиотеки, фреймворки и правила создавать бывает небанально, да. Легко можно накосячить и таких «правил» насоздавать, что все станет очень печально для всех. Поэтому плюсы «раскрываются» в основном в крупных проектах и крупных компаниях, где есть кому этот процесс провести аккуратно. А для проектов поменьше стоит просто использовать продуманные кем-то другим фреймворки — там будут и удобные инструменты и разумные правила и документация. Хороший пример такого фреймворка — Qt, особенно в 4й версии.
                                                                                +3

                                                                                Ну про возможность написать свои правила это вы загнули — для этого в с++ инфраструктуры в общем-то нету. Сравните с тем же C#, где дополнительные анализаторы кода устанавливаются автоматически с библиотеками, автоматически подсвечивают ошибки в IDE, запускаются при сборке, блокируют сборку в случае обнаружения проблем. Вот, например:


                                                                                1) https://github.com/xunit/xunit.analyzers/blob/master/README.md (список диагностик: http://xunit.net/xunit.analyzers/rules/)
                                                                                2) https://github.com/Suchiman/SerilogAnalyzer/blob/master/README.md


                                                                                Работает это через Roslyn, который по сути compiler-as-a-service докрученный до состояния когда в него можно добавлять плагины просто зарефернсив их. В с++ такое в некотором объёме умеет clang, но он не часть стандарта, да и по юзабилити ему далековато вроде бы.

                                                                                  0
                                                                                  Все отлично пишется, я Вас уверяю. Куча потенциальных проблем проверяется еще на этапе компиляции, а большая часть остального — в рантайме. Это я Вам говорю как человек занимающийся высокопроизводительными параллельными вычислениями, где вопросы синхронизации потоков порождают крайне сложно отлавливаемые баги. После перехода на правильный фреймворк мы годами (!) больше не сталкивались с проблемами многопоточности, подавляющее большинство разработчиков даже не задумывается о том что их код работает параллельно. Единственное что плохо в плюсах — так это генерация сообщений об ошибках compile-time. Юзабилити из-за этого действительно не очень — проблему-то компилятор найдет, а вот за расшифровкой того как ее починить может потребоваться консультация у опытного программиста :).
                                                                                    0
                                                                                    Если не секрет, какой фреймворк для многопоточности вы используете?
                                                                                      +1
                                                                                      Свой собственный, основанный на модели акторов
                                                                                  0

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

                                                                                    +2
                                                                                    Да нет, просто грамотно спроектированная система типов. Например если интерфейс предусматривает передачу владения объектом через указатель то там должен использоваться не голый указатель а unique_ptr. И всё — компилятор заставит Вас убедиться при использовании такого интерфейса что Вы именно передаете объект. Или допустим есть какой-то идентификатор, аналогичный по сути int-у. Создаем кастомный класс, у которого убраны лишние операции и приведение к другим типам — и компилятор проверит что аргументом функции выступает именно идентификатор а не случайный int или результат арифметической операции. Некая функция может вызываться только в конструкторе, т.к. потом это не потоковобезопасно? Добавляем соответствующую runtime-проверку. Потоковобезопасность обеспечивается через read-only доступ? Проектируем интерфейс так чтобы он предусматривал передачу владения на объект после которой получить к нему доступ можно только через const-указатели. Никакого программирования на темплейтах не нужно.

                                                                                    Вот сообщения об ошибках — это да, там все нередко печально :)
                                                                                  0

                                                                                  Можно просто писать адекватно.
                                                                                  А если уже упираешься в какой-то "затык" — начинаешь напрягать голову.
                                                                                  Конечно, можно так написать, что потом не только голову, но и ноги коллеги сломают, потому простое правило приходит с опытом "чем проще читается — тем лучше, пока профайлер не доказал обратное" + пару аксиом(хотя некоторые из них сейчас уже не так актуальны) в порядке сложности типа: передавать по константной ссылке, не возвращать указатели на переменные на стеке, использовать RAII по возможности, умные указатели, поменьше потоков и точек синхронизации, паттерны типа Actor и т.п.

                                                                                  +1
                                                                                  Аргументы против move несколько натянуты:

                                                                                  Я не хочу тратить усилия на реализацию move-семантики для всех типов, владеющих ресурсами
                                                                                  Для ~95% таких типов можно переиспользовать unique_ptr, реализовав только custom deleter.

                                                                                  Я не хочу иметь во всех своих типах пустое состояние. Часто оно не к месту. Бывает, что его сложно или невозможно добавить. И всегда это лишние усилия на поддержку
                                                                                  «Moved from» это не совсем «пустое состояние». Объект должен уметь получать новое значение и корректно удаляться, обнулять вообще всё нет необходимости.

                                                                                  Даже если move-семантика реализуема, она может быть непозволительна из-за того, что мы хотим раздать указатели на этот объект
                                                                                  Как вы сами отметили, trivially_relocatable тут ничем не поможет. Guaranteed NRVO — это прекрасно само по себе, но раздача указателей на локальный объект, возвращаемый из функции — это как-то очень вычурно.

                                                                                  Даже если перемещение допустимо, будет затрачено время на то, чтобы «занулить» первоначальный объект, и потом удалить его по всем правилам
                                                                                  В общем случае это обнуление одного владеющего указателя. Небесплатно конечно, но вряд ли будет узким местом.

                                                                                    0
                                                                                    «Moved from» это не совсем «пустое состояние». Объект должен уметь получать новое значение и корректно удаляться, обнулять вообще всё нет необходимости.

                                                                                    Ну, в этом-то и проблема.


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

                                                                                    Это не в общем случае, а в частном случае для unique_ptr.

                                                                                      0

                                                                                      Теоретически, можно при перемещении сделать relocation для всех членов и сделать какую-нибудь минимальную пометку о том, что объект moved-from, и его можно только удалить. Правда, нам для этого нужно выделить дополнительный bool, или знать tombstone bit pattern в одном из членов.

                                                                                    +1
                                                                                    Ваше предложение (P2025) кажется классным, но вы уверены в его реализуемости? Ведь это потребует некоторого control-flow анализа на фронтэнде, и, к примеру, gcc прямо сейчас с ним не справляется: gcc.godbolt.org/z/kMKQq5
                                                                                      0

                                                                                      В реализуемости уверен, но вот насколько сложно это будет реализовать в GCC или MSVC, понятия не имею. Control-flow анализ не потребуется: необходимо, чтобы на протяжении времени жизни переменной не возвращалось ничего иного. Такой анализ можно было бы практически через регулярку реализовать, если бы не условие, что "возврат иного" внутри нерабочих веток if constexpr — не в счёт.

                                                                                        +1

                                                                                        Это не такой уж простой анализ. Я же правильно понимаю, что в этом (https://gcc.godbolt.org/z/fKFT6d) случае


                                                                                        X f() {
                                                                                            if (false)
                                                                                            {
                                                                                                X b { "b" };
                                                                                                return b;
                                                                                            }
                                                                                            X a { "a" };
                                                                                            return a;
                                                                                        }

                                                                                        copy elision тоже гарантируется? А как насчет goto? (https://gcc.godbolt.org/z/HB09nk).


                                                                                        X f() {
                                                                                            if (false)
                                                                                            {
                                                                                        label:        
                                                                                                X b { "b" };
                                                                                                return b;
                                                                                            }
                                                                                            X a { "a" };
                                                                                            goto label;
                                                                                            return a;
                                                                                        }
                                                                                          0

                                                                                          В первом примере, copy elision гарантируется для b, так как его potential scope (грубо говоря, время жизни переменной) включает statements на строках 4-5, среди которых все return возвращают только b. Гарантируется для a, так как в его potential scope (строки 8-9) все returns возвращают только a.


                                                                                          Во втором примере, аналогично, всё гарантируется. Когда мы делаем goto label, перепрыгивая до объявления переменной a, её объект уничтожается, так что на его место преспокойно может отправиться b.

                                                                                            +2

                                                                                            Спасибо за объяснение, кажется, что действительно во всех случаях компилятор может выполнить copy elision. Правда, остается проблема, что сейчас для каждого компилятора есть пример, когда он эту отпимизацию не выполняет. То есть в отличие от guaranteed RVO, который был просто стандартизацией существующей практики, уже реализованной во всех компиляторах, чтобы поддержать ваш proposal вендорам придется поработать.

                                                                                      0
                                                                                      Правда, с синтаксисом P0573 есть проблемы, но я готов предложить несколько других вариантов, к тому же, более коротких:
                                                                                      1. для начала уберите синтаксический мусор из «текущего варианта»
                                                                                      2. Существует тьма предложений по укорачиванию лямбд и все отсекаются по одной причине — слишком сложный семантический анализ. Что-то типа «x y: x + y;» — проклятие для компилятора.
                                                                                        +1
                                                                                        И не только для компилятора. Визуально глазу не за что зацепиться, не всегда понятно, что именно делает такой «до предела сокращенный» код, как и в вышеупомянутом предложении со «спецсинтаксисом» типа ":=".
                                                                                          0

                                                                                          Представьте типичную задачу обработки набора данных на Ranges, с парой шагов map transform, filter и завершающим reduce accumulate. На обычных лямбдах:


                                                                                          auto result = iota(1, 1000)
                                                                                              | transform([](int x) { return x * 2; })
                                                                                              | filter([](int x) { return x % 3 == 0; })
                                                                                              | accumulate(0, [](int x, int y) { return std::abs(x - y); });

                                                                                          На сокращённых лямбдах:


                                                                                          auto result = iota(1, 1000)
                                                                                              | transform(x: x * 2)
                                                                                              | filter(x: x % 3 == 0)
                                                                                              | accumulate(0, x y: std::abs(x - y));

                                                                                          Во втором варианте в 2 раза меньше синтаксического мусора.


                                                                                          Ну или представьте некопирующий вариант заполнения вектора:


                                                                                          auto v = vector(10, [&] { return factory.make(); });
                                                                                          auto v = vector(10, :factory.make());

                                                                                          Разве не мило?


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

                                                                                            0
                                                                                            ваш синтаксис сломается на первом же нетривиальном юзкейсе — можете прикинуть ассимптотику парсинга конструкции &x; &&y;: x += y;? Я ставлю на NP
                                                                                              0

                                                                                              Во-первых, сокращённые лямбды не предполагают явного указания типов параметров, возвращаемого типа и прочих атрибутов обычных лямбд — вместо этого лямбда автоматически ведёт себя ровно так, как её тело.
                                                                                              Например, &x, &&y: x += y ошибка — правильно x y: x += y.
                                                                                              Во-вторых, при таком синтаксисе, очевидно, нужно ограничение, что expression-statement не может начинаться с сокращённой лямбды, так как : используется в метках. Так что, как и сейчас:


                                                                                              • :42; — это ошибка
                                                                                              • x: x * 2; — это метка x: плюс x * 2;
                                                                                              • x y: x + y; — это объявление переменной y типа x + далее ошибка

                                                                                              Плюс, вот ещё юзкейс — создание форматированной строки только в случае возникновения ошибки:


                                                                                              assert(a == b, :format("a != b, a={}, b={}", a, b));
                                                                                              assert(a == b, [&] { return format("a != b, a={}, b={}", a, b)); });
                                                                                                0
                                                                                                Во-первых, сокращённые лямбды не предполагают явного указания типов параметров...
                                                                                                тогда имо ваше предложение бесполезно — по моему личному опыту параметры лямбд чаще передаются по ссылкам, чем по значению
                                                                                                Так что, как и сейчас:
                                                                                                вы докажите не отсутствие синтаксического конфликта, а то, что такие лямбды можно распарсить с ассимптотикой не хуже чем полноформатные.
                                                                                                  0
                                                                                                  тогда имо ваше предложение бесполезно — по моему личному опыту параметры лямбд чаще передаются по ссылкам, чем по значению

                                                                                                  Я так понимаю, в «сокращенном варианте» все параметры лямбд как бы передаются через auto&&.
                                                                                                    0
                                                                                                    тем не менее передавать параметры по значению тоже иногда хочется
                                                                                                      0
                                                                                                      Да, согласен. С моей точки зрения, неявные автоссылки в параметрах и автоматический неявный захват всего на свете через [&] выглядят апасна, особенно если лямбда будет передана куда-то в такое место, которое «еще всех нас переживет». Это еще одна из причин, почему мне не очень нравится этот синтаксис. Kotlin — это, извините меня, штучка с GC со всеми его плюсами и минусами, там можно наплевать на такого рода вещи, а тут нет.
                                                                                                        0

                                                                                                        Идея "прозрачных лямбд" (transparent functions) предполагает, что выражение лямбды ведёт себя ровно так, как будто бы его вставили прямо на место вызова (за исключением того, что параметры вычисляются только один раз). Если вам нужна копия, приведите параметр к не-ссылочному типу. P0849 упрощает создание копии до auto(param).


                                                                                                        Если вы передаёте лямбду в место, которое «еще всех нас переживет», то пропишите явный список захвата, сокращённые лямбды это тоже поддерживают.

                                                                                                          0
                                                                                                          вы когда передаете куда-то лямбду вы хотите добиться какого эффекта — чтобы она работала так, как вы написали, или в зависимости от контекста, в котором она вызывается? Если Б, то вы уверены, что готовы читать исходный код какого-нибудь std::ranges::filter?
                                                                                                            0

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


                                                                                                            template <typename R, std::invocable<element_t<R>> F>
                                                                                                            auto filter(R&& r, F&& f);

                                                                                                            Тогда если ваша лямбда не может "съесть" element_t<R>, то компилятор вам так и скажет.


                                                                                                            Кстати, IDE стоит поддерживать концепт std::invocable и подписывать рядом с auto параметрами переданной лямбды то, чему эти параметры на самом деле будут равны. Примерно так:


                                                                                                            filter(vector{1,2,3}, x int& : x > 0)


                                                                                                            Жаль, сейчас ни одна IDE этого не делает. Надо закинуть идею разработчикам.

                                                                                                              0
                                                                                                              Жаль, сейчас ни одна IDE этого не делает. Надо закинуть идею разработчикам.

                                                                                                              https://youtrack.jetbrains.com/issue/RSCPP-24125 (пункт 1.3)

                                                                                                                0
                                                                                                                Тогда если ваша лямбда не может «съесть» element_t, то компилятор вам так и скажет.

                                                                                                                [](std::string s) { ... };
                                                                                                                [](const std::string& s) { ... };
                                                                                                                обе лямбды могут «състь» string&. Вот только в одном из этих случаев будет ненужное копирование
                                                                                                                  0

                                                                                                                  Разве это не аргумент в пользу сокращённых лямбд? Ведь они не копируют неявным образом.

                                                                                                                    0
                                                                                                                    в случае std::string — да. А вот какой-нибудь std::string_view лучше передавать по значению, а не по ссылке.
                                                                                                                      0

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


                                                                                                                      Лямбды в 99% случаев инлайнятся в вызываемую функцию, так что разницы, где именно сделали копию, не будет.

                                                                                                                        +1
                                                                                                                        Если вы считаете, что в вашем конкретном случае будет эффективнее сделать копию, её всегда можно сделать явным образом.
                                                                                                                        «давайте сделаем синтаксис для половины случаев короче на 1 символ, а во второй половине вы можете дописать еще шесть и надеяться на оптимизатор».
                                                                                                +2

                                                                                                Это уже Питоном пахнет.


                                                                                                auto result = iota(1, 1000)
                                                                                                    | transform(lambda x: x * 2)
                                                                                                    | filter(lambda x: x % 3 == 0)
                                                                                                    | accumulate(0, lambda x y: std::abs(x - y));

                                                                                                Хотя можно для такого случая и юникод расчехлить:


                                                                                                auto result = iota(1, 1000)
                                                                                                    | transform(λ̅ x: x * 2)
                                                                                                    | filter(λ̅ x: x % 3 == 0)
                                                                                                    | accumulate(0, λ̅ x y: std::abs(x - y));

                                                                                                По-моему, лучше, чем вариант "неожиданная [ => пытаемся парсить как лямбду".

                                                                                            +1
                                                                                            Хм.

                                                                                            Если компилятор опознает, что объект после того, как из него сделали move, не используется, он может убрать действия по зачистке объекта в пустое-но-валидное состояние — делается это через dead store elimination, или как оно там будет называться в конкретных реализациях.

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

                                                                                            Выглядит, что в статье пытаются найти пути поставить телегу впереди лошади? Где я неправ?
                                                                                              +1

                                                                                              Обычно у объекта переведенного в пустое-но-валидное состояние потом вызывается деструктор. То есть для оптимизации компилятору нужно догадаться, что сумма 2-х действий: "зачистка" объекта (как правило, случающаяся внутри move constructor или move assignment) + вызов деструктора у "зачищенного" объекта это noop. Чтобы догадаться нужно много и интенсивно инлайнить, на практике такое далоко не всегда происходит.

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