Pull to refresh

Comments 315

По поводу плагинов в виде .dll/.so: приходилось использовать и очень активно. Вначале использовались свои обертки вокруг «родного» API OS, затем перешли на работу посредством ACE.
А можете сообщить подробности? Коротким сравнительным примером простого кода. Чтобы воочию увидеть суть различий и возможные преимущества/недостатки.
Подробности забылись где-то в районе 2004-2005 годов, когда мы заменяли свои древние, самописные велосипеды (которые из которых росли еще со времен OS/2) на ACE. Основная причина была в том, что глупо было тянуть собственные разработки и заниматься их багфиксингом и модернизацией, когда все готовое уже было в ACE. Включая и средства для работы с DLL.
Скажите, пожалуйста, хотя бы, что такое ACE? ;-)
Adaptive Communication Environment — очень крутая для своего времени кросс-платформенная библиотека, абстрагирующая C++ разработчика от низкоуровневых деталей конкретных ОС.

Сейчас выглядит ну очень олдскульно и зачастую может быть заменена Boost-ом или POCO. Тем не менее, если знаний C++ недостаточно для разбирательства с потрохами Boost-а на какой-нибудь богом забытой платформе, то ACE вполне может быть достойным выбором и сегодня. ИМХО. конечно.
Когда я смотрел на код библиотеки std, мне всегда казалось, что его писали инопланетяне. У меня есть один принцип: я никогда не использую в своих проектах код, который выглядит ужасно.
UFO just landed and posted this here

Он очень красивый (нет)


    explicit vector(size_type _Count)
        : _Mybase()
        {   // construct from _Count * value_type()
        if (_Buy(_Count))
            {   // nonzero, fill it
            _Alty _Alval(this->_Getal());
            _TRY_BEGIN
            _Uninitialized_default_fill_n(this->_Myfirst, _Count, _Alval);
            this->_Mylast += _Count;
            _CATCH_ALL
            _Tidy();
            _RERAISE;
            _CATCH_END
            }
        }
Внимание, вопрос: как вы думаете, а почему код написан именно так? Какие на то причины?
чтобы в отладчике сразу понять, что вы залезли не туда? =)
Ниже практически всё подметили:
* нижние подчеркивания перед всем что только можно — чтобы защитить себя от пользователей, пишущих макросы в нижнем регистре
* вместо throw/try/catch — макросы, так как многие пользователи используют стандартную библиотеку с выключенными исключениями
* большие и длинные тела if — оптимизация под конкретный компилятор (разработчик знает где у него hot path и знает какую ветку компилятор делает hot path). Некоторые стандартные библиотеки имеют в телах goto по той же причине — знают косяки своего компилятора и написали goto чтобы сгенерировался более оптимальный код
* Множество макросов так же делают библиотеку юзабельной для разных версий стандарта. Так libstdc++ работает на флагах -std=(gnu++98,gnu++03,gnu++11,gnu++14,gnu++1z,c++98,c++03,c++11,c++14,c++1z), без макросов напободие _GLIBCXX_NOEXCEPT просто не обойтись.
* Есть макросы, которые позволяют работать без RTTI (так любят делать embedded разработчики)
* Другие странности форматирования (например после открывающей фигурной скобки отступ в -4 пробела) связаны с очень древними кодовыми базами… когда мониторы были узкие и вся С++ строчка иначе на экран не помещалась
* Куча плясок с бубном вокруг аллокаторов (поэтому много всякого метапрограммирования и специфичные typedef для указателей, вместо T*). Все для того, чтобы пользовательские аллокаторы с using pointer = fancy-pointers работали
* Куча плясок с бубном вокруг точек кастомизации — поэтому часть методов помечены как ::std::, другие методы вызываются без указания namespace
* Много плясок с бубном вокруг исключений — практически всё что пользователь передал в качестве шаблонного параметра может кинуть исключение в любой момент. После такого исключения надо оставаться в валидном состоянии (да, даже если функция хеширования кинула исключения — надо подчистить ресурсы и остаться в валидном состоянии).
* Некоторые имплементации поддерживают дебажные версии стандартной библиотеки, из-за чего макросов становится еще больше, а странность кода возрастает в угоду производительности в дебажном режиме
*… (я что-то наверняка забыл)
Причина одна, — недостаточно качественный язык, в котором нельзя выразить этот код нормально, без макросов.

Понятно, что скорее всего для совместимости с разными стандартами языка и разными моделями исключений, но код ужасен.
Это не макросы (в коде выше)
В середине 90-х слышал байку, что реализацию STL для MS VC++ писал большой любитель Lisp-а и делал он это на каком-то вполне себе читабельном DSL-е. Из которого уже генерировалось вот этот вот все непотребство с нечитабельными именами. Причем нечитабельные имена были выбраны специально. Вроде как даже несколько причин было для этого: начиная от того, что первые реализации STL должны были работать даже на компиляторах без поддержки namespaces, и заканчивая тем, чтобы не возникало конфликтов имен у пользователей, если кто-то отнаследуется от std::vector и захочет добавить парочку своих атрибутов в класс-наследник.

Понятно, что байка. Но когда заглядываешь в исходники некоторых реализаций STL в нее начинаешь верить :)

Тем не менее, об STL-е нужно судить не по коду самого STL (там вполне ожидаемо буде хардкор, непонятный половине действующих C++ников), а по коду, который использует STL.
Вроде как наследоваться некомильфо из-за отсутствия виртуальных деструкторов, не?
Все не так просто. В двух словах: такое наследование не будет проблемой, если нет удаления наследника через указатель на базу. Что, вообще-то говоря, нужно бывает далеко не всегда. Плюс, в современном C++ тот же shared_ptr временами может закрывать и эту проблему.

Тот момент, когда сначала заминусили за мнение, а потом залайкали за конкретный пример...

Будем восстанавливать справедливость :)

UFO just landed and posted this here
Майерс «Эффективное использование STL»
http://cpp.com.ru/meyers/ch7.html

В глаза бросается упоминание шаблона std::_Tree. В Стандарте ничего не сказано о шаблоне с именем Tree, но мы помним, что имена, начинающиеся с символа подчеркивания и прописной буквы, зарезервированы для авторов реализаций. Перед нами один из внутренних шаблонов, используемых в реализации некой составляющей STL.

Оказывается, практически во всех реализациях STL стандартные ассоциативные контейнеры (set, multiset, map и multimap) строятся на основе базовых шаблонов. По аналогии с тем, как при использовании string в диагностике упоминается тип basic_string, при работе со стандартными ассоциативными контейнерами часто выдаются сообщения с упоминанием базовых шаблонов. В данном примере этот шаблон называется _Tree, но в других известных мне реализациях встречались имена tree и _rb_tree, причем в последнем имени отражен факт использования красно-черных (Red-Black) деревьев, самой распространенной разновидности сбалансированных деревьев, встречающейся в реализациях STL.


Интересно, а если бы пользователь библиотеки (он же программист) мог бы сам выбирать реализацию шаблонов и алгоритмов, то как это всё выглядело бы?

Глядя на текущий код реализации, вы действительно хотите это знать?

Он классный (нет)


    iterator _Insert_n(const_iterator _Where,
        size_type _Count, const value_type& _Val)
        {   // insert _Count * _Val at _Where
 #if _ITERATOR_DEBUG_LEVEL == 2
        if (_VICONT(_Where) != this
            || _VIPTR(_Where) < this->_Myfirst
            || this->_Mylast < _VIPTR(_Where))
            _DEBUG_ERROR("vector insert iterator outside range");
 #endif /* _ITERATOR_DEBUG_LEVEL == 2 */

        size_type _Off = _VIPTR(_Where) - this->_Myfirst;
        if (_Count == 0)
            ;
        else if (_Unused_capacity() < _Count)
            {   // not enough room, reallocate
            if (max_size() - size() < _Count)
                _Xlen();    // result too long

            size_type _Capacity = _Grow_to(size() + _Count);
            pointer _Newvec = this->_Getal().allocate(_Capacity);
            size_type _Whereoff = _VIPTR(_Where) - this->_Myfirst;
            int _Ncopied = 0;

            _TRY_BEGIN
            _Ufill(_Newvec + _Whereoff, _Count,
                _STD addressof(_Val));  // add new stuff
            ++_Ncopied;
            _Umove(this->_Myfirst, _VIPTR(_Where),
                _Newvec);   // copy prefix
            ++_Ncopied;
            _Umove(_VIPTR(_Where), this->_Mylast,
                _Newvec + (_Whereoff + _Count));    // copy suffix
            _CATCH_ALL
            if (1 < _Ncopied)
                _Destroy(_Newvec, _Newvec + _Whereoff);
            if (0 < _Ncopied)
                _Destroy(_Newvec + _Whereoff, _Newvec + _Whereoff + _Count);
            this->_Getal().deallocate(_Newvec, _Capacity);
            _RERAISE;
            _CATCH_END

            _Count += size();
            if (this->_Myfirst != pointer())
                {   // destroy and deallocate old array
                _Destroy(this->_Myfirst, this->_Mylast);
                this->_Getal().deallocate(this->_Myfirst,
                    this->_Myend - this->_Myfirst);
                }

            this->_Orphan_all();
            this->_Myend = _Newvec + _Capacity;
            this->_Mylast = _Newvec + _Count;
            this->_Myfirst = _Newvec;
            }
        else if ((size_type)(this->_Mylast - _VIPTR(_Where))
            < _Count)
            {   // new stuff spills off end
            value_type _Tmp = _Val; // in case _Val is in sequence

            _Umove(_VIPTR(_Where), this->_Mylast,
                _VIPTR(_Where) + _Count);   // copy suffix

            _TRY_BEGIN
            _Ufill(this->_Mylast,
                _Count - (this->_Mylast - _VIPTR(_Where)),
                _STD addressof(_Tmp));  // insert new stuff off end
            _CATCH_ALL
            _Destroy(_VIPTR(_Where) + _Count,
                this->_Mylast + _Count);
            _RERAISE;
            _CATCH_END

            this->_Mylast += _Count;
            _Orphan_range(_VIPTR(_Where), this->_Mylast);
            _STD fill(_VIPTR(_Where), this->_Mylast - _Count,
                _Tmp);  // insert up to old end
            }
        else
            {   // new stuff can all be assigned
            value_type _Tmp = _Val; // in case _Val is in sequence

            pointer _Oldend = this->_Mylast;
            this->_Mylast = _Umove(_Oldend - _Count, _Oldend,
                this->_Mylast); // copy suffix

            _Orphan_range(_VIPTR(_Where), this->_Mylast);
            _Copy_backward(_VIPTR(_Where), _Oldend - _Count,
                _Oldend);   // copy hole
            _STD fill(_VIPTR(_Where),
                _VIPTR(_Where) + _Count, _Tmp); // insert into hole
            }
        return (begin() + _Off);
        }

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

Что будет, если подвергнуть этот код пресловутому рефакторингу?

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

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

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

Дальше вываливается пару килобайт ошибок в консоль

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

Какую-такую "нормальную IDE" рекомендует использовать коммитет стандартизации с++? Может они начнут нам рассказывать, какими инструментами нам пользоваться?

И, последнее, вы приводите код из майкрософтовской реализации и критикуете язык с++ что этот код плохо написан?

т.е. вот этот код лучше?

Какую-такую «нормальную IDE» рекомендует использовать коммитет стандартизации с++?

При чем тут комитет? Нормальную IDE вам советую я. Причем даже не какую-то определенную
т.е. вот этот код лучше?

да
А ведь libc++, который вам советовали, действительно симпатичнее даже «вот этого» кода.
</irony>

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


И да, читать код стандартной библиотеки просто не надо. Достаточно cppreference.com.

И настроек компилятора — к слову о _TRY_BEGIN/_CATCH_END
можно легко пережить наличие у std::vector приватного метода с названием _M_default_append. Но вот чего понять не могу: зачем аргументы функций-то с __ начинаются?

Чтобы защититься от вещей вроде #define x.


Из неочевидных вещей, от которых стандартной библиотеке приходится защищаться, это например, перегрузка операторов "&" и ","

Вы даже не представляете, что порой люди пишут:
#define count int // В фирме Одуванчик принято макросы писать с маленькой буквы

struct vector {
    vector(size_t count); // Боль...
};
зато у вас самая большая база юнит-тестов.
https://habrahabr.ru/company/yandex/blog/323972/#comment_10132820

Его не придётся читать как только добавят концепты в основные компиляторы.
Помню пытался выяснить почему в GCC STL unordered_map не желает принимать incomplete types при декларации. Так и не понял — адская смесь из разных наследующихся друг от друга и от соседского Шарика классов. И без единого комментария — что особо убивает.

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

Они страдают молча

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

Дайте пожалуйста ссылку на место в документации, где сказано про поддержку incomplete types в различных контейнерах.

В стандарте есть:
An incomplete type T may be used when instantiating forward_list if the allocator satisfies the allocator completeness requirements 17.5.3.5.1

An incomplete type T may be used when instantiating list if the allocator satisfies the allocator completeness requirements 17.5.3.5.1

An incomplete type T may be used when instantiating vector if the allocator satisfies the allocator completeness requirements 17.5.3.5.1


Для map и unordered_map гарантий никаких нет. Если хотите добавить — опишите идею на https://stdcpp.ru/proposals

Спасибо, был не в курсе. К сожалению, честно купленной копии стандарта не имею. А cppreference.com в разделе про unordered_map об этом молчит.

Я-то задачу решил — использовав обычный мэп. Я про то, что код нынешних реализаций STL крайне тяжело читать. Что является проблемой, когда надо выяснить подробности такого вот implementation-defined behavior. Которым пестрит половина стандарта.

по факту все-таки иногда приходится смотреть в STL.

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

unordered_map<const string, string> прекрасно компилируется в GCC 4.9, но падает с ошибкой в Clang 8 (Apple).

Из-за разной реализации STL.
UFO just landed and posted this here
Когда я смотрел на код библиотеки std, мне всегда казалось, что его писали инопланетяне.… я никогда не использую в своих проектах код, который выглядит ужасно

А что вы используете в своих проектах вместо стандартной библиотеки?
UFO just landed and posted this here
код библиотеки std, мне всегда казалось, что его писали инопланетяне.… никогда не использую в своих проектах код, который выглядит ужасно

А что вы используете вместо стандартной библиотеки (вместо std::vector, std::unordered_map и std::string например)?
А можно посмотреть на ваш string?

У меня для вас плохие новости: вам не понравится. Свой string я писал последний раз в 2006. Те, кто минусует, должны еще раз прочитать фразу "в своих проектах". Это не то же самое, что "везде, где только можно".

В вашей строке ошибка: если вот тут выскочит исключение, то у вас произойдет double free и приложение рухнет.

если выскочит исключение по выделению памяти, то программа так и так не жилец.

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

Ну и приведите мне приложения, где это РЕАЛЬНО обоснованная трата бюджета разработки? Я делал движок для игрушечек, кого волнует его работоспособность в случае, если не хватило памяти?

  • Браузер и любые документо-ориентированные приложения. Мы можем закрыть один документ или вкладку, но остальные мы хотим сохранить.


  • Серверные и любые запросо- или транзакционно-ориентированные приложения. Мы выдадим ошибку в ответ на запрос, вызвавший переполнение памяти, и сервер продолжит работать с остальными запросами.

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

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

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

Простите. А Вы не можете мне в двух словах объяснить, что такое «аллокаторы»? Систематическое изучение C++ у меня впереди, и я понимаю, что аллокаторы — это (наверное) какие специальные функции или конструкции, которые выделяют память. Или… это что-то вроде «умных указателей»? (если попал «пальцем в небо», то не отчаивайтесь, а, просто, расскажите своими словами, будет понятнее, потом, читать книжку, а я Вам «спасибо» скажу)))
  1. STL allocators: https://en.wikipedia.org/wiki/Allocator_(C%2B%2B)
  2. переопределение operator new/operator delete/operator delete[] с использованием нестандартного менеджера памяти, который работает быстрее за счет увеличения расхода памяти/отказа от многопоточности/использования специфичных для приложения паттернов работы с памятью
  3. https://ru.wikipedia.org/wiki/Объектный_пул приложение преаллоцирует возможное количество "тяжелых" объектов и пытается работать с тем, что есть.

В контексте обсуждения, это, скорее, п.3

В вашей строке метод format не реентрабельный (им нельзя пользоваться из разных потоков одновременно).

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

UFO just landed and posted this here

очевидно же, что мне это не требовалось

UFO just landed and posted this here
Очевидно, что ваш код, в отличии от кода стандартной библиотеки, не работает. Понятный неработающий код хуже непонятного работающего.

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

а еще есть такое. Это писалось, когда еще не было varidatic templates, не скажу, что это читаемый код, но уровень жести зашкаливает по сравнению с std, где обычный класс std::vector выглядит, как будто по ошибке открыл бинарик.

Код stl должен:
быть const — корректным
быть exception — корректным
оптимальным (noexcept, move, минимум аллокаций)
устойчивым к #define'ам
удовлетворять требования максимальной алгоритмической сложности для всех методов
собираться на всех актуальных компиляторах
собираться при неполной поддержке стандартов
(при работе с памятью) поддерживать кастомные аллокаторы

Скольким из этих требований удовлетворяет ваш код?

Вы разве не умеете читать? Разве я такое утверждал? Разве я говорил, что мой код идеален и его можно использовать везде? Я лишь сказал, что в своих проектах я не использую код, который выглядит ужасно. И который содержит все эти ИЗЛИШКИ, про которые вы только что написали. В чем я не прав? Что вы мне пытаетесь доказать? То, что я и без вас знаю?

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

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

Нет, если тянуть информацию о типах в рантайм или писать на макросах.

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

поддержку чего?

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

а можно пример, где это не выполняется и где стандарт не помечает поведение как id/ub? Sequence points, емнип, в процессе, можете о них не упоминать

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

Модель памяти можно выбирать — используем свои аллокаторы. Какие алгоритмы вы имеете в виду?
Хорошо. А можно узнать, что такое
std::vector
— это шаблон или это встроенный тип? Дело в том, что от языка программирования ожидаешь, что в нём самом реализованы основные вычислительные концепции. Кажется, что так должно быть. Или, например, сортировка.Сортировка, как таковая, единственна, но есть множество алгоритмов сортировки. Что с этим делать? В STL мы, просто, вызваем нужный нам алгоритм и всё, так? А если мы хотим иметь пул методов и выбирать по ситуации, какой лучше? По моему, это — увод мною разговора в другую сторону, но я ещё подумаю, что именно меня интересует.

P.S. Ещё вспоминаю Borland C++ 5.0, где, вроде бы, были всякие контейнеры, но без шаблонов. Чем так страшно предоставление информации о типах в рантайме? И что будет, если попробовать разобраться в различных способах построения контейнерных типов? Вряд ли такой способ единственен…
Вы задаете вопросы, которые отлично разобраны в книге «Дизайн и эволюция языка C++». Пытаться отвечать вам здесь — это как пытаться пересказать изрядную часть этой книги своими словами.
«Дизайн и эволюция языка C++», говорите? О, это, как раз, то, что мне нужно. Буду читать с карандашом в руках. :-)
Кстати, а никто не пытался? Это мог быть целый цикл статей на Хабрахабре! Я видел только пару-тройку кратких обзоров.
Хорошо. А можно узнать, что такое
std::vector
— это шаблон или это встроенный тип?

есть простое эмпирическое правило. Видите std:: и не знаете что это — см. документацию.

Сортировка, как таковая, единственна, но есть множество алгоритмов сортировки.

Вам стандарт дает гарантию, что std::sort выполнится за
O(N·log(N)), where N = std::distance(first, last) comparisons.
(since C++11)

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

P.S. Ещё вспоминаю Borland C++ 5.0, где, вроде бы, были всякие контейнеры, но без шаблонов. Чем так страшно предоставление информации о типах в рантайме?

производительность ниже в несколько раз

Вообще говоря, для некоторых частных случаев, быстрее придумали.
(Есть алгоритмы сортировки быстрее встроенной в ст. библиотеку).
Например, поразрядная сортировка N K-битных чисел работает
за O(NK), вместо O(NK log(N)) при использовании std::sort().


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

UFO just landed and posted this here
Не существует алгоритмов, основанных на сравнении элементов, которые могут гарантировать меньшее, чем (n*logn) колличество сравнений. Это математически невозможно.

И как это противоречит моему комментарию?

UFO just landed and posted this here
Я подозреваю, что сложность выполнения алгоритмов — вещь довольно теоретическая. Практически, мы имеем дело с конкретными реализациями, и реальная быстрота выполнения алгоритма может весьма варьироваться. Обычно, в таких случаях, ориентируются на систему статистических испытаний. Не говоря, уж, о том, что, в зависимости от типов данных и от размеров данных будут существенно меняться и скорость выполнения операций, а это значит, что говорить о самом быстром алгоритме не приходиться. Все эти теоретические оценки асимптотические. Вот почему, реальное программирование — это искусство выбора оптимального решения в данных обстоятельствах. А, поскольку, каждый запуск приложения происходит в уникальных обстоятельствах, то и от приложения следует ожидать возможности выбирать различные пути реализации одной и той же операции. Вот я о чём.
На эту тему мне очень нравится эпическая драма в двух частях от Dr. Dobbs,
C++ and The Perils of Double-Checked Locking: Part I
C++ and The Perils of Double-Checked Locking: Part II
В которой рассматривается написание потокобезопасеного велосинглтона на С++

Хотя в С++11 нам упростили жизнь.

Кстати, а чем не устроил uint8_t из cstdint? И ещё — понятно, что таких случаев уже не найти, но обычно полагают что байт всё-таки может быть не восьмибитным на некоторых платформах; std::byte будет гарантировать 8, или он тоже будет формально назван платформенно-зависимым?

а чем не устроил uint8_t из cstdint

Тем что это unsigned char и соответственно is_same<uint8_t, unsigned char>::value == true.

И да, байт может быть не восьмибитным, и такие машины еще есть и работают. В std::byte столько же бит сколько бит в байте на целевой машине.
Но тогда и в unsigned char не 8 бит? В этом случае is_same снова true?
is_same сравнивает типы а не их размер.
std::is_same<int, int>::value                 // true
std::is_same<int, unsigned int>::value        // false
std::is_same<int, signed int>::value          // true
std::is_same<std::byte, char>::value          // false
std::is_same<std::byte, unsigned char>::value // false
std::is_same<std::byte, signed char>::value   // false

std::byte — это отдельный не арифметический тип данных.
Я знаю. Хорошо, переформулирую вопрос. На машинах со словом не равным 8 битам (я просто не знаю, простите) uint8_t тоже эквивалентен unsigned char?
Зависит от платформы.

Скорее всего на такой платформе не будет uint8_t, uint16_t, uint32_t, uint64_t. Стандарт С++ говорит что эти алиасы — опциональные.
В архитектуре NeuroMatrix sizeof(char) == sizeof(short) == sizeof(int). 32-битный байт во всей красе. Таким образом там char и short являются синонимами обычного int.
О, кстати об интах с размером. Можно сделать в будущих стандартах как-нибудь так, чтобы uint_fast8_t вёл себя как int, а не как char, и
uint_fast8_t num = 17; 
std::cout << num << std::endl;
не приводило к консольному апокалипсису?
Хороший вопрос. Попробуем решить.
TI TMS320 с размером байта 16 бит, uint8_t в stdint.h отсутствует. Думаю в cstdint его тоже не будет.
Тоже натыкался на техасовские процы (какой-то из C54xx) и байтовые проблемы: memcpy копирует не по одному байту, а по два, и соответственно размер в нее надо передавать sizeof(var)/2.

Странно, потому что sizeof обязан выдавать размер в char, то есть в данном случае в 16-битных словах, а не в байтах. На TMS320C40 например, все типы 32-разрядные, sizeof(char) == 1 но CHAR_BITS == 32

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

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66110
Чаще всего «слово» на машинах равно 16 битам. На x86 так точно
Тогда объясните в каких именно случаях предполагается использовать std::byte, а в каких unsigned char.
UFO just landed and posted this here
сначала unsigned char не 8 бит. в C byte определяется как unsigned char. так что правильней всегда думать, что байт — это unsigned char, а не пакет из 8-ми бит. на целевой платформе это может быть и 16 бит, и 11 и неизвестно ещё сколько. конкретные биты, их номера, упоминаются уже в ветвях #ifdef… #else… #endif, при реализации работы с битами для конкретных платформ.
А std::byte* можно будет алиасить с другими типами указателей?
Да. Для std::byte* те же правила по алиасингу, что и для unsigned char.
При инициализации std::function знак «равно» тоже можно не писать?
Да, но придется написать скобки
std::function   d{ [](int i) { std::cout << i; } };
// или
std::function   d( [](int i) { std::cout << i; } );
Ну, я это и имел в виду ,)

А эта красота работает только для нескольких стандартных классов? Или пользовательские тоже смогут?

Работает и для пользовательских. При том из коробки, если шаблонные параметры класса участвуют в конструкторе. Иначе придётся писать explicit deduction guide.
UFO just landed and posted this here
Весь заголовочный файл <codecvt> помечен как устаревший. В нём находилась вот это:
namespace std {
  enum codecvt_mode {
    consume_header = 4,
    generate_header = 2,
    little_endian = 1
  };

  template<class Elem, unsigned long Maxcode = 0x10ffff,
    codecvt_mode Mode = (codecvt_mode)0>
  class codecvt_utf8
    : public codecvt<Elem, char, mbstate_t> {
  public:
    explicit codecvt_utf8(size_t refs = 0);
    ~codecvt_utf8();
  };

  template<class Elem, unsigned long Maxcode = 0x10ffff,
    codecvt_mode Mode = (codecvt_mode)0>
  class codecvt_utf16
    : public codecvt<Elem, char, mbstate_t> {
  public:
    explicit codecvt_utf16(size_t refs = 0);
    ~codecvt_utf16();
  };

  template<class Elem, unsigned long Maxcode = 0x10ffff,
    codecvt_mode Mode = (codecvt_mode)0>
  class codecvt_utf8_utf16
    : public codecvt<Elem, char, mbstate_t> {
  public:
    explicit codecvt_utf8_utf16(size_t refs = 0);
    ~codecvt_utf8_utf16();
  };
}
я тихонько мечтаю о некоторых фичах С++, которые никогда не появятся.
Что это могло быть?
1) полноценные модули (видел в обсуждениях)
2) библиотека модулей (вытекает из первого). сейчас приходится рыскать на просторах интернета и перебирать библиотеки от посторонних авторов на посторонних ресурсах. Хотя в С++20 заявляны, возможно, некоторые классы. Побольше их на все случаи жизни.
3) функции типа GetHelpFunction(), которая предоставит инфу по функционалу. Желательно, на универсальном языке, понятном человеку. И тут возникает проблема — пока универсального языка не существует, есть варианты костылирования из уже существующих языков, типа того же английского (недостаток — его надо знать заранее и хорошо).

Поставил сверхсложные задачи (особенно третью). Т.к. я технарь, сложностей не боюсь — уже продумываю решение проблемы. Вариант универсального языка:
обучение в играх. Там вообще может не использоваться текст, только жесты и демонстрация к чему жест приводит. Чем не универсальный язык? Но он реализуется, скорее, средствами IDE, нежели средствами самого языка программирования. Но при таком подходе от текстового языка программирования можно отказаться (он используется, но не виден пользователю).
И тут возникает проблема — пока универсального языка не существует, есть варианты костылирования из уже существующих языков, типа того же английского (недостаток — его надо знать заранее и хорошо).

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

И еще не совсем понятно, что «хуже»: какие-то знания, или обучение.

Под библиотекой модулей — вы подразумеваете пакетный менеджер, по типу npm, pip, nuget итп? Если да, то я просто мечтаю о такой фиче. Реализуется сейчас частично отдельными независимыми разработчиками инструментов, так, например, в VS есть "NuGet для C++"


Последнее непонятно, зачем функция справки на уровне языка?


На мой взгляд самый логичный вариант — использование метаданных, как в C#, с описанием функции, аргументов итп. На английском.

Я начал сейчас ковырять conan, пока очень доволен, но он еще молод и активно развиваться. https://www.conan.io/
Проблема conan-ов, vcpkg, CPM и др. подобных вещей в том, что им требуется централизованный репозиторий пакетов или репозиторий с метаописаниями пакетов. В условиях, когда нет одного доминирующего де-факто стандарта на управление зависимостями это неудобно. Плюс к тому, на Linux-ах разработчики используют зачастую штатные менеджеры пакетов и в сторону внешних инструментов, вроде conan-а или cpm-а смотрят неодобрительно.

Имхо, удобнее, когда управление зависимостями делается без необходимости куда-то загружать пакет или его описание. Например, когда зависимости разруливаются через ExternalProject_Add в CMake. Но только в более человеческом виде :) Используем нечто подобное уже около года — очень удобно, по крайней мере для подключения OpenSource проектов, которые можно забирать с github, bitbucket или sourceforge.

В других языках проблема c документацией решена: javadoc

А всякие ifstream с wstring и раньше же работали?

Это было нестандартное расширение в MS STL

А остальным и не нужны недокидорвки, т.к. есть UTF-8

Проблема в том, что стандарт С++ не говорит о том, в какой кодировке однобайтовые строки. И не говорит, как нижележащее системное АПИ должно их интерпретировать. Современные линуксы используют UTF-8, там всё просто. А Windows использует однобайтовую системную локаль, с непредсказуемым результатом.

Куча всего по constexpr вводят, даже обсуждают constexpr аллокации в куче. А хорошего метода для отладки шаблонного кода (constexpr кода в чуть меньшей степени) нету, помимо топорного static_assert и type_traits (хорошо, что ещё появился if constexpr).


Было бы здорово, если бы появилось что-то по типу static_warning. Оно, в сочетании с будущим $reflect (который, насколько я знаю, будет способен вытащить имя типа с присутствующими квалификаторами) могло бы чуточку поспособить отладке и пониманию кода, который разворачивается в момент компиляции.

Приходите на встречу. Как раз буду рассказывать о планах на constexpr_trace и constexpr_assert для С++20.
Да. Но не уверен что будет способ задавать вопросы удалённо.

А можно ли оставлять вопросы на сайте?

Мне тут nataliamakarova773 подсказала, что «на youtube можно будет задавать вопросы, будем озвучивать докладчикам».

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

Бьёрн говорил, что пока отказались из-за проблем с совместимостью.
Долго гуглил, но так и не смог понять: какой из вариантов корутин обсуждается? Было два: бустовый и microsoft'овский, и точно были холивары, который лучше и правильнее. Будут у нас слова co_async, co_await или нет?
«Гор-рутины» как бы намекают :)
Спасибо большое за огромную проделанную Вами работу!

Вопрос насчёт Number TS: правильно ли я понимаю, что мы будем иметь эффективную длинную арифметику из коробки?
Используете ли вы в компании, или в своих личных проектах, плагины/динамическую-загрузку-библиотек? Опишите в комментариях, какими функциями и классами вы для этого пользуетесь?

Да, используем. В проекте плагинная система построена на базе CTK Plugin Framework.

Вопрос насчёт dll: пользуемся и очень активно. Используем для этого WinAPI.
Что-нибудь слышно насчёт интроспекции / рефлексии?
С рефлекшеном всё сложно, но работы активно ведутся.
А не знаете, хоть приблизительно, как это реализовать планируют? А то как-то рефлексии и C++ вместе как-то в голове не укладываются.
UFO just landed and posted this here
при желании все укладывается, как в движках id хотя бы
Есть несколько предложений в комитет, причём одно из них достаточно новое. Но базируются они вот на этом решении: https://github.com/matus-chochlik/mirror
С контрактами всё так же, т.е. никак?
На этой встрече обсуждали контракты: одобрили их в EWG, отправили на рассмотрение в LEWG (где скорее всего просто помусолят тему «интеграция контрактов в стандартную библиотеку)», далее отправят в CWG.

Все шансы что контракты будут в C++20.
Комментарий к опросу
Да, плагины, динамические библиотеки использовались как в своих, так и в рабочих проектах компаний. Ранее для этого были свои обертки над функциями WinAPI или POSIX. Как правило, это были кроссплатформенные обертки с реализаций под Win/Nix.
Не так давно в boost все же появилась поддержка динамических библиотек. Теперь использую ее.

Не по опросу ...
Возможно эта информация прошла мимо, но как мне могло показаться, в C++17 опять не вошла рефлексия. Эта тема для меня очень интересна. По моему мнению, появление рефлексии в C++ дало бы много возможностей для разработки прикладных библиотек без «костылей», с помощью которых можно было бы сделать компактно сервиализацию, работу с БД (ORM), реализацию RPC и разных REST API.

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

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

Шаблоны — боль. Это как если в России всё делопроизводство велось бы на китайском языке. Или в Китае — на русском. Мозги приходится выворачивать в ортогональную плоскость. Но здесь видимо уже ничего не попишешь. Или шанс есть?
UFO just landed and posted this here

Для начала хотелось бы концепты. Причём не лайт, с ограничением только "наружу", но и с ограничением "внутрь".

Вот вам хочется иметь концепты. Так хочется и мне. А люди, жалующиеся на шаблоны, как на что-то сложное и не похожее на С, скорее и концепты проклянут.

Эмм… они проклянут фичу, которая вместо листинга ошибки инстанцирования на несколько экранов покажет type X does not satisfy concept Y at file.cpp:42?

Тогда они выбрали не тот язык.

ИМХО, скучно и мелко.

Я бы хотел доработок по части SIMD и выровненных данных (до сих пор какие-то танцы с бубном). Ещё доработанные корутины, а не то что сейчас есть (лучше пусть просто дадут стандартизированный способ сохранить стэк, дальше сами разберёмся).

Новые форматы для чисел кому-нибудь пригодятся, но очередной контейнер со скрытыми аллокациями — гадость. Хватит лепить из C++ подобие Java! Уже столько наплодили вредных сущностей для «программистов на пол-ставки» из разряда «лепи не думая, рантайм сам с памятью разберётся». std::string, std::vector, std::function… Теперь у нас даже числовые типы будут память выделять!

Преимущество C++ в предсказуемой производительности, а эти фантазёры всё пытаются догнать Java, C# и прочие «удобные» языки.
UFO just landed and posted this here
vec.push_back(a);

Вы сможете сказать, будет тут выделена память или нет? Не сможете, потому что это а) зависит от внутреннего состояния vec б) зависит от конкретной стратегии std:vector на данной платформе.

Это нормально в языках со сборкой мусора, но неприемлимо в языках с «ручным» управлением памятью. Править такой код от юных (или не очень) падаванов делающий аллокации на каждый чих — удовольствие ниже среднего. И чем дальше, тем больше становится способов вызвать скрытое выделение памяти. Тот же std::function соединённый с std::bind и вроде как невинным ссылочным типом превращается… правильно, в ещё больше аллокаций.

Зато такой код писать быстро и удобно, налепил контейнеров из библиотек, в которых лежат другие контейнеры — и пошёл чай пить. Пусть авторы ОС беспокоятся о том как код работает.
UFO just landed and posted this here
>Ну вообще-то могу. Я могу проверить, если capacity меньше, чем размер вектора, тогда выделения памяти не будет.

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

Вы смотрите в код и видите такую строчку. Будет ли выделена память? А фиг его знает, надо (как вы сами сказали) ковырять состояние объекта на этот момент.

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

У меня не было бы претензий к std::vector если бы его не сделали «дуракаустойчивым». То есть если бы push_back() при нехватке места вызывал бы падение, а все места выделения были бы обозначены вызовами resize(). Но нееет, у нас же тут типа Java, всё должно быть «удобно» и «автоматически», чтобы даже хреновый код от студентов-первокурсников мог как-то работать.
UFO just landed and posted this here
>Сколько будет выделено памяти? Вы не знаете. И я не знаю. Для этого и сделаны структуры данных, которые выделяют память по требованию.

Опять передёргиваете, вопрос был не о количестве выделенной памяти, а о самом факте выделения. В вашем примере всё очевидно. Хотя надо заметить, что даже и в таком виде в программе должно быть ограничение «сверху», тогда мы сможем сказать «здесь точно выделится не более MAX байт памяти».

>Это идёт против самого определения структуры данных «вектор». Вы путаете вектор (не std::vector, а структура данных «вектор») с обычным массивом.
Ничего я не путаю. Вы бы по-прежнему смогли изменять размер массива как и раньше.

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

Это и так происходит каждый раз при push_back(), просто скрытно от программиста. В моём варианте программист бы сам проверял выделенный размер когда ему нужно.

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

Опять передёргиваете, вопрос был не о количестве выделенной памяти, а о самом факте выделения.

Далеко не всегда выделение памяти — узкое место в программе. А там, где это действительно боттлнек (и профайлер показал вам на это), никто не мешает вам:
а. использовать собственные аллокаторы
б. резервировать память вручную
в. пользоваться обычным массивом (или std::array)
г. не пользоваться компонентами, аллоцирующими в куче
Но не для C++, где фишкой языка является предсказуемость, из которой следует полная ответственность программиста за ход выполнения своей программы.

А что, соответствующие стандарту классы как-то непредсказуемо себя ведут? Полный контроль, как я уже упомянул, имеет смысл только по результатам работы профайлера. Иначе можно год писать то, что делается за 2-3 недели
UFO just landed and posted this here
Если выделение памяти и низкоуровневый контроль не имеет значения, то нет причин писать на C++. Он чрезвычайно неудобен, очень дорог в плане программистских ресурсов, ненадёжен, требует большого количества строк для выражения элементарнейших вещей, плохо портируем, имеет длинные итерации при разработке (компиляция, линковка, повторить сначала) и тащит за собой кучу хлама из 70х годов.

И это при том что я люблю C++. Просто у него есть одна киллер-фича по сравнению с другими языками — никаких сюрпризов при выполнении программы. Вся программа выполняется дословно так, как её задумал программист.
требует большого количества строк для выражения элементарнейших вещей
Можно пример, а лучше два?
nums = map(int,open("file.txt").readlines())

(Я не про то что python лучше c++, я про то что многие вещи на питоне просто короче)

Я так понимаю, что речь лишь про то, что можно делать посредством стандартной библиотеки C++? То, что под конкретную задачу можно сделать удобную библиотеку на C++ в принципе не считается?
А теперь то же самое, но с обработкой ошибок

Да, для C++ можно сделать/найти библиотеку которая может также.
Да, программа отработает на целый 1% быстрее.
Да, может то что важно мне, не важно другим (я школьник)
Но вот недавно мне был нужен скрипт, который просто сгенерирует табличку
25х25.
Писать такое на плюсах — извращенство.

Писать такое на плюсах — извращенство.
А кто-то обязывает делать именно так?

Я вам страшное скажу — у языков программирования есть свои области применения, вне которых от языка мало пользы. Задача программиста, как инженера, еще и в том, чтобы подобрать подходящий инструмент для своей задачи. Так что применять C++ для простых скриптов так же дико, как и Python для СУБД уровня MongoDB или ClickHouse.

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

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

Но это не меняет того, что одни и те же вещи делаются на питоне раза в 3 короче.
Какие вещи, можно полюбопытствовать? Покажите, пожалуйста, аналог MongoDB на Python-е с кодом в 3 раза короче.

Или речь идет только про однострочники в студенческих лабораторных?

Конечно нет "аналогов MongoDB на Python-е", писать БД на питоне — тоже извращенство.
Но если плюсы такие из себя удобные и выразительные, то почему живут и здравстувуют JavaScript/Python/Java/etc?

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

Поэтому когда кто-то говорит, что C++ недостаточно выразителен, хотелось бы посмотреть на альтернативы в тех же областях, где для C++ пока еще есть место.
Но если плюсы такие из себя удобные и выразительные, то почему живут и здравстувуют JavaScript/Python/Java/etc?

Но если JavaScript/Python/Java/etc такие удобные и выразительные, то почему живет и здравствует с++?

1) Мало памяти (умная мусорная корзина)
2) Вычисления
3) Олимпиады
Наверняка есть и другие области, где с++ распространен.
И требования этих областей важнее выразительности

UFO just landed and posted this here

А что вы предлагаете?
Писать


if(vec.size()==vec.capacity()){
    vec.resize(vec.size()*2);
}
vec.push_back(a);

вместо


vec.push_back(a);

?
Вы в любом случае не сможете проконтролировать выделение памяти, если, скажем, это web-сервер.

vec.push_back(a);

Вы сможете сказать, будет тут выделена память или нет? Не сможете, потому что это а) зависит от внутреннего состояния vec б) зависит от конкретной стратегии std:vector на данной платформе.

Если нужно чтобы элементы хранились непрерывно и:
* если вы точно знаете, сколько элементов вам нужно — используйте array/stack_vector
* если вы знаете, сколько в среднем хранится элементов — используйте small_vector/vector+reserve
* если не знаете — используйте vector

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

Если это не так — опишите пожалуйста поподробнее вашу идею и вашу реализацию контейнера.
Идея очень простая — отрезать от вектора слово «автоматически». Никаких автоматических скрытых аллокаций, никогда, сделать это невозможным на уровне интерфейса. Всегда только vector+reserve.
UFO just landed and posted this here
Я не соглашусь, как раз по тому что у современных языков много неофитов они как раз и пишут «гуманитарный код», заматеревший разраб может может скрутить некрономиканское откровение из любого ЯП.
Приведите примеры новых фич, которые непонятны.
Я не увидел ничего, что может вызвать ступор и не становится понятным через пару минут чтения описания.
Здесь проблема в многоязычии, нет больше языка сопоставимого по функциональности и выразительности с #cplusplus, а остальные языки реализуют, по своей сути, подмножество, и если кто-то хочет понять программу другого, то приходится менять мировоззрение и методику программирования под соответствующий язык, а в случае #cplusplus еще и понимать как эти трюки реализуются в другом(их) языке(ах), или использовать общее подмножество, что делает программирование на низкоуровневом языке, как минимум, сомнительным занятием.
Почти любой практически используемый язык можно более-менее понимать даже если в первый раз его видишь.

Посмотрите на код практически любых контейнеров Java, C#, C (где контейнеры через макросы) или Rust.

@antoshkka А что с C#? Имхо 99% всей сложности языка из-за макросов существуют. А шарп 2.0 от шарпа 7.0 для пользователя отличается только незначительным сахаром. Ну там, свойства можно инициализировать не в конструкторе, а прямо при объявлении, да экстеншн-методы пописывать… Самое сложное, что есть в языке — yield и async/await, ни в какое сравнение со сложностями плюсов не идет. Да, системы усложняются, появляются фреймворки, все такое… Но сама база не меняется никогда. И это очень серьезно ограничивает сложность: нетрудно разобраться с любым бизнес-фреймворком, если каждый элемент по-отдельности понятен и не изменился с момента академической юности.

А шарп 2.0 от шарпа 7.0 для пользователя отличается только незначительным сахаром.


Кортежы, именованные поля кортежей, structured bindings, шаблоны с is, шаблонные switch… Это только некоторые нововведения 7 C#. Когда я заглядывал в код контейнеров 5 лет назад — множество ключевых слов C# я просто не знал, т.к. большинство пользователей их не пишут в повседневном коде и в институте за год их не успели объяснить.

Написать знакомому программу на C# было адом — абсолютно непонятны времена жизни объектов в лямбдах, примитивы синхронизации немного отличаются, и их приходится учить заново…

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

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


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

Честно говоря, не представляю, какие страшные ключевые слова могли там встретить — public/readonly/get/static? Я брал за отсчет C# 2.0 как по факту первый используемый в повседневном использовании. Так вот, с тех пор (то есть более чем за 10 лет) в языке появились ключевые слова var/dynamic/nameof, и собственно всё. Ну еще linq-синтаксис которым никто не пользуются, сюда эти where/let, но они контекстные и как я уже сказал — неиспользуемые.


Все изменения языка, как и его структуру, можно изучить ну за 2-3 недели точно. После чего можно 10 лет на шарпе не писать, а потом вернуться и продолжить с того момента, где остановился.


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

Да. Вывод верный. Только вот вопрос не в том, сложен ли язык, если ты его не знаешь, а в том, насколько легко выучить язык и поддерживать знание на определенном уровне. Я вот например уверен, что шарп я — знаю. Да, я могу не знать какие-то тонкости работы стандартных классов, могу не ответить сходу на вопрос "как сделать так, чтобы вызвались 3 разных перегрузки" или еще что-нибудь в таком роде, но в целом язык — извините, но да, знаю. В тех же плюсах людей которые могут с уверенностью сказать, что знают язык — нет в принципе. Не потому, что язык сложный, а потому что макросы позволяют его менять. И поэтому если я вижу в шарпе if я ЗНАЮ, что дальше будет открывающая скобка, условие, и закрывающая скобка). В то время как на макросах можно спокойно спрятать те же скобки в этот самый макрос и получить такой вот "руби-стайл".


Это я и написал, сложность плюсов в том, что под этим названием скрывается целый класс мета-языков, и код одного "плюсовика" будет совершенно непонятен другому. С шарпами и прочими строгими языками такое невозможно. Поэтому я и хотел узнать, в чем сложность шарпа? У него немного ключевых слов, у него нет макросов, которые могли бы поменять структуру программы, раз в 2-3 года добавляется немного сахара, которые в 99% случаев интуитивно понятны (за исключением, может быть, async/await), и я просто не вижу того, что может создавать сложность. Потому что озвученные проблемы "абсолютно непонятны времена жизни объектов в лямбдах, примитивы синхронизации немного отличаются" просто от непонимания того, что в языке с GC эти вопросы неважны в общем случае и отданы на откуп компилятору, но ведь это концепция языка. Эта концепция не меняется никогда и узнав её один раз, она остается с тобой до конца жизни языка.

Если что, я не считаю шарп святым граалем и не холиварю, сравнивая его с плюсами, просто это мой основной рабочий инструмент. Но это относится и к любому другому сравнимому по мощности языком, ну пусть будет Rust. Да, встроенный синтаксис у раста один из самых сложных из всех существующих мейнстримных языков. Но его макросы работаю с AST и не позволят сделать свой язык на базе плюсов. И это — большой плюс (вернее большой минус плюсов в том, что он это позволяет). Да, это дает некоторую гибкость, но цена за это — непомерна. Именно из-за этого язык местами не похож сам на себя, код стандартной библиотеки должен учитывать пользовательские макросы и выглядеть очень странно, и именно это и создают всю знаменитую сложность плюсов: потому что макросы (да еще и с темплейтами) порождают просто целое семейство языков там, где должен был быть один.

Ну, OK. Допустим, что вы правы в оценке проблем C++. Выход из этой ситуации в чем?

Так я и не обещал выход, я отвечал на


Посмотрите на код практически любых контейнеров Java, C#, C (где контейнеры через макросы) или Rust.
Что это попросту не так. Ну а сточки зрения языка тут ничего не сделать, джинн выпущен из бутылки. Точно так же, как легаси в виде winapi-интерфейсов, как легаси в виде текстового обмена в *nix, как legacy-коллекции в C# (до выхода дженериков в 2.0). Я лично не клялся в верности ни одному языку программирования. И переход с С++ на тот же раст должен быть достаточно простым. Да, там есть новые концепции, которые надо выучить, но чем больше язык построен на концепциях (они же абстракции), а не на конкретной имплементации/стандартной либе/этц, тем проще им пользоваться, потому что знания не пропадают, а аккумулируются и синергируют.

Так что, хотя я и не собирался, но все же отвечу: для С++ как языка выхода особого нет, кроме как на уровне каких-то гайдлайнов запрещать пользоваться каким-то подмножеством языка. Но это не поможет понять ошибку в стандартных классах или неправильном их использовании. С точки зрения С++-программистов выход есть и еще какой — тот же Rust это и есть C++, в котором смартпойнтеры на уровень языка добавили да решили попытаться сделать автоматическую управляемую детерменированную работу с памятью вместо new/delete. Так сказать, зачем нам С++ из которого мы гайдлайнами вырежем плохой функционал, если можно получить всё на блюдечке вместе с нормальным пакетным менеджером, приятными ништяками вроде паттерн матчинга…

Так я и не обещал выход

Так а никто и не сказал, что ваш диагноз верен и что вы, действительно, правы ;)
С точки зрения С++-программистов выход есть и еще какой — тот же Rust это и есть C++

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

С++ сильно продвинулся в этом направлении. Даже если брать более короткий временной интервал после 2003-го года, то в языке появилось больше средств для безопасной работы:
* auto для вывода типа переменных (устраняют ошибки вроде int l = strlen(s));
* range-for (нет целого класса ошибок с выходом за рамки массивов из-за плюс-минус единички);
* лямбды (делают удобным использование стандартных алгоритмов вместо ручных циклов, которые были источниками ошибок);
* variadic templates (сильно уменьшают надобность в макросах);
* rvalue references и move-semantic (сильно упрощают, например, прием и возврат «тяжелых» значений);
* std::array вместо C-шных массивов;
* std::unique_ptr (исправляет косяки std::auto_ptr);
* auto для вывода типа возвращаемых значений и типов в полиморфных лямбдах (опять же уменьшают надобность в макросах).

Если плотно сидишь на C++11/14, а потом внезапно возвращаещься в рамки C++98/03, то очень быстро выясняешь, насколько сильно упростился язык в повседневном применении.

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

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

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


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

С++ сильно продвинулся в этом направлении. Даже если брать более короткий временной интервал после 2003-го года, то в языке появилось больше средств для безопасной работы:

Я не прошу прекращать работу комитета. Просто сейчас С++ из локомотива инноваций превратился в догоняющий язык (когда уже будет фича А, когда уже будет фича Б, ...). Это не плохо и не хорошо, просто естественный процесс. А комитет упрощает боль разработчикам, которым нужно поддерживать легаси С++ (опять же имхо). Возвращаться на старую версию языка всегда неприятно, тут что С++, что не С++, разницы не вижу. Что он развивается — это очевидно, вопрос в том, сколько в нем изначально фатальных недостатков. Кто-то в них признается, кто-то предпочитает не замечать, но рано или поздно новый язык становится настолько хорош, что люди пересиливают свою инертность и начинают миграцию. Так было с С, так было с С++, так было с джавой/коболом/..., так в ближайшее время будет а растом или еще каким-нибудь еще "более лучшим" языком. Стоит ли от этого печалиться? Вряд ли, учитывая, что все языки разиваются, а переход на другой возможен только когда он прям ваще вкусный. А это значит — меньше боли нам, разработчикам.

просто такое вот мое мнение
Ну так применительно к статье про новый стандарт C++ и про предложения для следующего стандарта кроме констатации «все пропало» хотелось бы услышать какие-то идеи «по спасению». Если идея в том, что «пора валить» на Rust, ну OK.
Просто сейчас С++ из локомотива инноваций превратился в догоняющий язык
Просто интересно, когда это C++ был «локомотивом инноваций»?
новый язык становится настолько хорош, что люди пересиливают свою инертность и начинают миграцию. Так было с С, так было с С++, так было с джавой/коболом/...
Боюсь вас огорчить, но и C, и C++, и Java и даже COBOL продолжают активнейшим образом использоваться.
Просто интересно, когда это C++ был «локомотивом инноваций»?

Когда он появился. Можно долго рассуждать на тему, что в Smalltalk'е это было за миллион лет до плюсов, остальные идеи были взяты тоже откуда-то еще, но скомпоновалось это весьма удачно именно в плюсах. И последующее их развитие, темплейты и вот это все — это и было отличное развитие.


Боюсь вас огорчить, но и C, и C++, и Java и даже COBOL продолжают активнейшим образом использоваться.

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

Можно долго рассуждать на тему, что в Smalltalk'е это было за миллион лет до плюсов, остальные идеи были взяты тоже откуда-то еще, но скомпоновалось это весьма удачно именно в плюсах.
Вы прям заинтриговали. Что именно из C++ было в Smalltalk-е задолго до того как? А то я, например, все еще нахожусь под впечатлением, что C++ позаимствовал многое из Simula, ML и Ada, но отнюдь не из Smalltalk.
Смотря что мы имеем ввиду под «использованием».
Наличие business-critical приложений, прекращение работы которых станет заметным для большого количества людей. Которые нуждаются в поддержке и развитии, и которые не могут по каким-то причинам быть переписаны на более современных языках программирования.
По крайней мере про кобол мне страшно даже думать, что кто-то на нем еще пишет.
А вы наберитесь смелости, представьте себе такое. Может категоричности по отношению к тому же C++ станет поменьше.
Вы прям заинтриговали. Что именно из C++ было в Smalltalk-е задолго до того как? А то я, например, все еще нахожусь под впечатлением, что C++ позаимствовал многое из Simula, ML и Ada, но отнюдь не из Smalltalk.

Мне казалось, ответ очевиден.


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

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

Мне казалось, ответ очевиден.

Мне кажется, вы в очередной раз ошибаетесь. Если для вас «очевидный ответ» — это ООП, то C++ позаимствовал ООП из Simula, а не из Smalltalk. Тут можно еще много чего наговорить про различия между ООП для статически типизированных ЯП и для динамически типизированных, а так же о том, почему Smalltalk имеет совсем косвенное отношение к ООП для статически-типизированных языков, а так же почему Smalltalk считают «родителем» ООП не смотря на то, что та же Simula появилась гораздо раньше. Но это здесь явно офтоп.
Ну, так можно хоть аду считать живым ЯП.

Ладно бы вы COBOL отказывались признавать живым. Но назвать Ada мертворожденной… Это сильно. От человека с таким кругозором критика C++ начинает играть новыми красками.

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

Жизнеспособность, востребованность, используемость и, уж тем более, популярность — это все слабосвязанные друг с другом вещи. Ada вполне себе живой язык, востребованный и используемый, развивающийся (последняя версия стандарта от 2012-го года), с регулярно обновляющимися инструментами для разработки. То, что это не попадает в поле вашего зрения, не меняет объективного положения дел.

У вас и по поводу C++ какое-то избирательное суждение: чего не видите, того не существует, что не популярно, то не жизнеспособно. Отсюда и выводы о том, насколько хреново все в C++ и что поэтому C++ники должны искать выход в Rust-е (как до этого в C#, а еще до этого в Java, а еще до этого в Delphi, а еще до этого в Eiffel, ...). Остается только порадоваться, что C++ развивается не смотря на мнение таких судильщиков. Ибо вы явно даже отдаленно не представляете себе, сколько кода на C++ уже написано, как долго он будет использоваться и развиваться, и сколько еще на C++ напишут до того, как тот же Rust перейдет в категорию действительно широко используемого инструмента.

Спор ни о чем. Просто у людей закостенелое отношение. Вот писал он на плюсах 10 лет, человеку уже не интересно изучать что там за монады или higher kinded polymorphism, ему бы auto и модульность получить — уже неплохо. А это определяет рынок программистов, из которых и выбирают компании. Допустим тот же раст сейчас молод, допустим через 3 года он победит все детские болезни. Итого в 2020 году не будет ни одной причины начинать новый проект на плюсах, потому что раст ничем не хуже, во многом — лучше, а весь пласт уже существующего кода просто подключается через кросс-языковые механизмы. Но люди будут их делать. Почему? А просто из-за костности мышления. Своё старое привычное болото всегда лучше. Типичный парадокс блаба.


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


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

Мы ведь уже выяснили, что понятия "востребованный" и "живой" — различаются для нас. Для меня например живой язык имеет хотя бы 1% вакансий на площадках вроде hh.ru, все остальные — эзотерика и история.

Типичный парадокс блаба.

При таком уровне аргументации остается только спросить: сколько вам лет?

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

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


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

А что не так с уровнем?

Например, продемонстрированное ранее отсутствие кругозора, категоричность и максимализм. А так же откровенные заблуждения.
Я расписал все: есть язык Х (не обязательно плюсы, абсолютно любой), если есть язык У который ничем не хуже и позволяет прозрачно переиспользовать функционал Х, то смысла писать на Х нет никакого.

Вы забываете о существующих наработках и разработках. Если для языка X есть мегабиблиотека, которая позволяет за пару рабочих дней (а то и часов) собрать решение под какую-то типовую задачу с немного различающимися условиями, то языком X будут продолжать пользоваться даже после того, как аналог появится на языке Y. Хотя появление этого самого аналога — это еще большой вопрос.
Например, появление хороших вычислительных библиотек на C++ отнюдь не мешает продолжать использовать Fortran на серьезных вычислительных задачах. А наличие для C++ того же Eigen-а отдаляет пришествие Rust-а в эту нишу на неопределенное время.
C++ мог бы использоваться в разработке ОС даже на самом низком уровне, т.к. позволяет практически тоже самое, что и С. Тем не менее, ядра Linux и FreeBSD как разрабатывались на C, так и продолжат разрабатываться. И заменить C, который еще более древний и убогий, чем C++, Rust-у здесь так просто не получится.
Изначально речь шла вообще о другом, а именно о том, что и шарпы, и остальные перечисленные языки намного проще в изучении, чем плюсы.
Так а какой практический вывод из этого? Бросить заниматься C++ и уйти всем на более молодые языки, при проектировании которых использовался опыт того же C++?
Ваша проблема в том, что вы считаете такой уход не просто разумным, но и неизбежным. А для многих текущих C++ проектов это совсем не так.

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

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

Это не справедливо для Y = Rust и X = C++. Ну или надо уточнить, что подразумевается под "свободно переиспользовать функционал".

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

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

Это не так уж просто и удобно.


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

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

Допустим тот же раст сейчас молод, допустим через 3 года он победит все детские болезни

вы хотите сказать, что за 3 года раст достигнет большего, чем с++ за 40 лет?
а весь пласт уже существующего кода просто подключается через кросс-языковые механизмы

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

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


вы хотите сказать, что за 3 года раст достигнет большего, чем с++ за 40 лет?

Раст уже давно появился — раз. Экспоненциальный рост технологий никто не отменял — два.

ИМХО с С++11 код во многих местах стал понятнее. range-based for и auto вместо страшных итераторов, using вместо typename упрощают жизнь новичкам.

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

Если же сразу учить C++, без отсылок на C, то все гораздо проще. Когда людей учат сразу использовать std::array или std::vector вместо старых массивов, когда range-for или std::for_each+лямбда используются вместо старых for(i=0;i<ASIZE(a);++i), когда из функции возвращается std::pair<bool,std::string> вместо возврата отдельно bool-а и отдельно вручную аллоцированной строки через out-параметр и т.д., и т.п., то современный C++ осваивается гораздо легче, чем тот же C++98/03.
Хочется, чтобы язык поощрял (не знаю, как лучше назвать) говнокодпрототипирование. Я восхищаюсь красотой и изящностью C++, который впитывал модные веяния на протяжении десятков лет развития. Но написать прототип быстрее на чём-нибудь другом, где можно
[line.rstrip('\n') for line in open('filename')]
, где класс уже содержит методы почти на все случаи жизни. Qt, мне кажется, движется в этом направлении.
Да, это, быть может, не про сам язык, а библиотеки, но язык задаёт направление, собирает определённую группу людей вокруг себя. И если язык не разрешает std::list::operator[], т.к. он будет провоцировать людей на медленные действия, а предлагает писать std::advance(std::list::begin(), 5), то и либы будут писаться такие же «универсальные», но долго дорабатываемые напильником.
И если язык не разрешает std::list::operator[], т.к. он будет провоцировать людей на медленные действия
Было бы здорово в таком случае использовать условные директивы:
#define I_WANT_TO_USE_SLOW_METHODS

Как с unsafe в C# программист берет на себя ответственность за использование такого кода
Ещё никто не мешает написать классы-обёртки для внутреннего использования. В принципе, наверное, там и варнинги можно как-то выводить, что мол, пофиксите прототип. :)

Но по факту, времени на кардинальную переработку алгоритма после написания — как правило, не бывает.
Я не знаю, правильно ли это, но у меня создалось впечатление, что довольно часто принято писать прототипы на чем-то простом, чтобы быстро сделать черновик (Matlab, Julia, Python в конце концов), а потом уже писать чистый код на С++.
Мы в РГ21 тоже надеялись и голосовали за их принятие на заседании. К несчастью, к консенсусу на заседании не пришли.
Да, было бы необычно, но очень уж многообещающе выглядело, главным образом скорость компиляции выросла бы. Жаль что не попали в С++17.
Огромное спасибо за вашу работу, очень люблю плюсы.

Используете ли вы в компании, или в своих личных проектах, классы способные хранить числа произвольной длинны (unbounded integers) с нестандартными аллокаторами?

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

Остальные задачи потрясают своей колоссальностью, но очень бы хотелось примитивную рефлексию enum и enum class.
Пример:
enum SomeState {
    Undefined = -1,
    AllOk = 1,
    SomeStrange1,
    SomeStrange2,
    ...
};
...
// Неинициализированное состояние -- какое угодно, хотелось бы 
// что-то вроде "Undefined = -1: default", тем более слово default зарезервировано, 
// в основном, для перечислений.
    SomeState s;
    std::cout << s << std::endl; // неизвестно что выведет

// Вывод значения сейчас:
    if (s != AllOk)
        std::cout << "Not correct state: " << s << std::endl;
    // Выведет "Not correct state: 2"
// Хотелось бы:
    if (s != AllOk)
        std::cout << "Not correct state: " << std::enum_to_string(s) << std::endl;
    // Пусть выводитдет "Not correct state: SomeStrange1"
...
// Чтение из бинарного потока:
    ostream >> s; // если прочитано что-то, не то, в переменной будет мусор.
// Хотелось бы:
    try
    {
        ostream >> s; // если прочитано что-то, не то, бросается исключение.
    }
    catch (const std::enum_exception &e)
    {...}
...
// Как следствие предыдущего, кастовать:
    SomeState s;
    int sint = 10;
    try
    {
        s = enum_cast<SomeState>(sint);
    }
    catch (const std::enum_exception &)
    {....}



Надо на уровне компилятора, стандатрной библиотеки держать:
    T enum_cast<T>();
    class std::enum_exception;
    если совсем расслабиться :default  на к-л значение в перечислении.


P.S. часто слышу, что плюсы не те, так как /нет сборщика/устаревший/не следит за памятью/over 9000 причин/. Считаю, что Стандарт работает как эталон для всех остальных.

// Неинициализированное состояние -- какое угодно, хотелось бы
SomeState s;

не согласен с таким синтаксисом: пусть будет выведен мусор, что угодно и как угодно — это только объявление, на него может даже процессорная команда не потребуется, а инициализация — это уже дополнительные телодвижения, но ваше предложение с default при таком раскладе:


SomeState s{};

уже имеет смысл, что бы там оказалось SomeState::Undefined. Так как, по идее, сейчас там будет 0, но такого элемента в перечислении нет: http://ideone.com/XehYqs

constexpr аллокацию «в куче»

Ждем, надеемся, верим
Используете ли вы в компании, или в своих личных проектах, плагины/динамическую-загрузку-библиотек? Опишите в комментариях, какими функциями и классами вы для этого пользуетесь?

QPluginLoader.

Достаточно будет и кроссплатформенной QLibrary

QLibrary немного для другого. Как правило, если библиотека загружается динамически, то это плагин. С которым удобнее работать через QPluginLoader. Который, кстати, позволяет запросить метаданные до загрузки плагина
Никогда не понимал, зачем std тянуть в стандарт языка. Язык отдельно — либы отдельно. Тот же самый пример с std::optional должен был решиться на уровне баг-фикса, а не через создание комиссии.
И так смотрю версия C++14 занимает почти 1400 страниц.
operator<=>()

Оператор «хрен знает», судя по виду :)

Такими темпами скоро будет arbitrary operator definition. a #$^ b >>= c ^&* d.

// P.S.: В примерах выше занк '=' тоже можно не писать :)

мелкая описка :)

Будем ждать больше вкусностей в следующем обновлении :)

Не дождался я светлого будущего и ушел на Rust. Как-то мне кажется, что после С++14 плюсы куда-то не туда пошли. Если С++11 был прорывом, то дальше пошла стагнация и конца ей не видно.

Немного не согласен со стагнацией: всё-таки C++11 весьма долго принимали и прорывом он казался, по сути, на фоне предыдущего "отсутствия развития". Думаю, что ещё не все процессы устаканились и потом комитет будет работать эффективнее. Опять же, бесконечно "революционные" фичи выдавать не реально. Когда-нибудь дело дойдёт и до модулей и т.д.


Хотя я тоже "ушел на Rust", но за развитием плюсов следить интересно.

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

Все эти десятки бумаг по контрактам ещё будут устаканиваться долгое время, [[implies(std::nothrow)]] появится не скоро. Сокро появятся только нечто наподобие:
void push(int x, queue & q)
  [[expects: !q.full()]]
  [[ensures: !q.empty()]]
{
  //...
  [[assert: q.is_valid()]];
  //...
}
Контракты я использовал в Eiffel-е и, частично, в D. Во-первых, это не совсем про то. Контрактами, например, нельзя показать, что функция чистая. Или что она нерекурсивная. В том же D ввели отдельный атрибут pure, который к контрактам не относится. Во-вторых, что было для меня неожиданностью: Eiffel располагает к использованию контрактов, там это происходит само собой, естественным образом. А в D контракты хоть и были, но нужно было заставлять себя придерживаться дисциплины и описывать их.

Ну и дело вообще-то не в том, чтобы [[implies(std::nothrow)]] появился. А чтобы движение началось :)
В gcc есть атрибуты pure, const, и много других. Хотя не уверен, что это то, что вы имели в виду.

https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes
Есть, что наводит на мысль о том, что [[implies(std::pure)]] вполне себе реализуемо. Осталось чтобы:

a) можно было выставлять требования для блоков кода (например, [[expects(std::pure)]]) с соответствующей диагностикой от компилятора в случае нарушения;
b) включения этого в стандарт, чтобы это было не только в одном конкретно взятом компиляторе.

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


  1. Опакечивание. Какой-то более-менее стандартный формат описания пакета. Для начала пусть без центрального репозитория — хотя бы как в Go возможность просто стянуть репозиторий, который будет собран по своим правилам и подключен к сборке моего проекта. Без попыток вручную скрестить CMake, MSBuild, GNU Make, Autotools, BJam etc.
  2. Chosen nightly compiler. GCC может концепты, MSVC может модули, CLang может что-то ещё нужное (не помню). Попробовать это всё в связке нельзя по понятным причинам.
  3. Если такая большая проблема добавить ranges v3, есть ли шанс в обозримом будущем получить хотя бы набор iterator adaptors? Втащить Boost не всегда есть возможность.

Я понимаю что в (1) и (2) требую слишком многого. Хотя нет — С++ единственный из мейнстрима, где нет конкретно (1) и катастрофический разброд касательно (2).

* Для (1) нужен работающий прототип, который всех устраивает. Такого нет и в ближайшем будущем не появится
* С (2) вы всегда можете помочь — допишите нехватающий функционал в GCC/Clang ;-)
* Пункт (3) ожидайте в скором времени во всех компиляторах поддерживающих концепты — ranges выпускаются в этом году в виде отдельного TS, который можно попробовать ещё до С++20.

(1) Я тихонько надеялся, что хотя бы о необходимости прототипа начинают задумываться. Впорчем, это отдельная тема для разговора.
(2) Вопрос в том, что даже фулл-тайм я не смогу написать прототипы для всего, что может войти в стандарт, для одного компилятора.
(3) Ключевое словосочетание "поддерживающих концепты". Последний раз, когда я интересовался вопросом, такое было только в отдельной экспериментальной ветке GCC. Нв других "столпах индустрии", насколько я знаю, состояние на нуле.

А для (2) фулл тайм и не требуется, вы недооцениваете свои силы:

* Один человек не напрягаясь в свободное время может разгрести тьму ошибок, до которых у разработчика просто не доходят руки (например ищите Mikhail Maksimov в changelog Boost. 1.64)

* Некоторые в свободное время просто берут, и добавляют недостающий им функционал. Вот например вам changelog GCC, в котором есть фраза «Thanks to Daniel Krügler, Tim Shen, Edward Smith-Rowland, and Ville Voutilainen for work on the C++17 support.». Что-то мне подсказывает, что эти ребята на GCC не работают.

Я могу тут приводить примеры до вечера. При том будут и матёрые дяди-пенсионеры, и студенты, и безработные, и люди вкалывающие на двух работах. Все они не работают фулл тайм, однако заменяют собой пару десятков фулл тайм разработчиков (за что им огромное спасибо).
  1. Так range-v3 вы можете использовать уже сейчас. Во всех нормальных компиляторах. Концепты там эмулируются старым добрыми добрыми шаблонами и макросами. Несколько дней назад авторы синхронизировали range-v3 с ranges TS.

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

Напишу про синтаксический сахар, которого мне иногда не хватает.

  • Многократный break для выхода из вложенных циклов/switch:
    for(int i = 0; i < 100; i++)
      for(int j = 0; j < 100; j++)
        if(some_check(i, j))break break;
    
  • Последним в цепочке break может стоять continue — в этом случае будет продолжен соответствующий цикл.
  • continue для switch:
    switch(num)
    {
    case 1:   do_smth(1);   continue 3;  // переход к выполнению case 3
    case 2:   do_smth(2);   continue 3;  // аналогично
    case 3:   do_smth(3);   break;
    }
    

Лучше break на метку, впрочем, для этого есть goto, внезапно.

А как Вам такой код:
A.for(int i = 0; i < 100; i++)
  B.for(int j = 0; j < 100; j++)
    if(some_check(i, j)) break (A);

— для выхода из двойного цикла, и
A.for(int i = 0; i < 100; i++)
  B.for(int j = 0; j < 100; j++)
    if(some_check(i, j)) break (B);

— для выхода только из вложенного цикла?

Обычно в таком случае пишут функцию, которая делает return. Тот же goto, но за него камнями не закидают, да и как завещал классик Мартин — довольно модульно.

Насколько я помню, был большой спор про UFCS — возможность вызывать свободные функции как методы классов, но передавая инстанс первым аргументом. Как с этим дела?


Мне, к примеру, пришёл в голову такой вариант:


a |> f(b, c)
// превращается в 
f(a, b, c)

т.е. через отдельный оператор. Хотя их, наверное, и так хватает.

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

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

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

Я не говорю ещё о визуальных компонентах и операционной системе.

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

Было бы очень хорошо! Всегда пытаешься «сварганить» нечто похожее доморощенными средствами. Но! Всё это потребует существенной «ломки» синтаксиса. Я бы мечтал о том, чтобы была возможность перегружать ",", "(", ")", ":" и многое другое, что позволило бы описывать прямо в коде конструкции произвольной сложности. Или всё это уже реализовано?

И можно ли в C++ делать что-то по типу оператора with? Например, так:
object{
.Open();
.Load();
.Close();
}

Что я ещё пропустил (из предыдущих серий)?

P.S. А ещё свойства (properties)! Есть ли какие-либо планы относительно свойств?
Лично мои планы относительно свойств очень масштабные: не допустить включения этой фигни в стандарт.
А можете пояснить, почему вы не хотите их видеть в стандарте?
Я пропустил очень много предыдущих серий и не знаю, что там, вообще, происходило со свойствами (если происходило).

Я имею в виду конструкцию, которая восходит ещё к Delpi:
__property AnsiString Name = { read=FName, write=SetName };

С этим элементом языка что-то не так?
Например очень многим они не нравятся: https://stdcpp.ru/proposals/07a6ff30-4f0e-4683-bbaf-4a635eadfb34

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

Поискал, не нашел. В чем же беда свойств? В шарпе они хорошо себя зарекомендовали, их постоянно разивают, не бросают как Legacy. У них же куча плюсов: их можно объявляь в интерфейсе (сверхполезнаа фича), на них можно вешать валидацию… Мне скорее кажется, что концепция полей не нужна — пусть компилятор сам имплементирует свойства самого низкого уровня как поля. А то получается путаница как в уже упомянутом шарпе: есть поля, есть свойства, в рефлексии они выглядят по-разному и т.п. Была бы одна сущность (с возможностями свойств) — было бы легче.

Свойства прекрасно показали себя в C# и поэтому просто должны быть добавлены в С++ по тем же причинам. Я уже правда использую свойства, т.к. меня не волнует standards compliance, __declspec(property) наше всё. Я даже сподвиг ребят из ReSharper C++ добавить генераторы для этого. Но хочется чтобы это все-таки вошло как часть языка.
UFO just landed and posted this here
UFO just landed and posted this here
Я бы мечтал о том, чтобы была возможность перегружать ",", "(", ")", ":"

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

Почему в опросе нет абсолютно никакого пункта про поддержку строковых переменных в Юникоде?
Вот уже двадцать лет, как хочется материться из-за того, что стандарты C++ полностью игнорируют UNICODE!

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

Есть 2 большие проблемы: 1. Это далеко не однозначная вещь, как это должно работать. Юникод гораздо сложнее чем может показаться. См. utf8everywhere.org там хорошо расписаны разные проблемы.


  1. Для поддержки юникода нужны довольно объемные таблицы (до мегабайт размером), которые не получится тащить на микроконтроллеры например. Поэтому поддержку юникода придётся делать опциональной.
довольно объемные таблицы (до мегабайт размером)

libicudata.so.58.2 весит 25+ MiB, остальные поменьше, мегабайты, а не десятки. Весь набор libicu без утилит весит 30+ MiB

Есть же QString из QT. Или хотя бы с Java слизать API по работе с юникодом. Оба варианта существуют больше 10 лет.

>> QString stores a string of 16-bit QChars, where each QChar corresponds one Unicode 4.0 character. (Unicode characters with code values above 65535 are stored using surrogate pairs, i.e., two consecutive QChars.)

Это qt 5.8 — так себе Unicode поддерживается QString'ом, не нужно ничего с таким качеством поддержки в стандарт совать.
я не в комитете, но имхо не нужно такого в стандарте, против я.
Юникод же так и работает. Если код символа слишком большой для одной позиции, то он кодируется двумя или более последовательными позициями. В Qt используется либо ICU, либо собственная реализация, в зависимости от флагов компиляции.
SIMD очень хочется нормальный. И в алгоритмах (желательно с поддержкой функций как в OpenMP, но как минимум simd execution mdoel для алгоритмов), так и на уровне типов данных (желательно с поддержкой адаптирующейся ширины, чтобы не делать Vec<Int, 4> для SSE, Vec<Int, 8> для AVX2 и Vec<Int, 16> для AVX512).
Мне кажется просто нужны нормальные операторы для операций над массивами, по аналогии с Intel SPMD: если я пишу a*b и a и b — это массивы, то операция должна векторизоваться. Ну а если матрицы, должна вызываться соответствующая рутина. Сейчас все это спрятано под ширмой MKL, но пользоваться этим намного более напряжно чем например в MATLAB. Вообщем проблема в том, что массивы/матрицы криво стандартизованы и неоптимально реализованы. А скалярные операции над SSE/AVX регистрами это более редкое явление.
Для классических матричных и векторных операций есть Eigen, но грамотно использованный SIMD позволяет ускорить широкий спектр алгоритмов не завязанных на матрицы и «длинные» векторы. Сюда относятся и трассировка лучей и модели соударений (NBody) и квантовая хромодинамика и много чего ещё со сложно-структурированными данными и/или глубокими гнездами циклов. В многомерных циклах далеко не все данные SIMD — часть обязательно будет скалярной. Это верно даже для умножения матриц. А для для массивов структурированных данных a*b — уже вообще не очевидная операция (даже если эта структура всего лишь комплексное число).

SPMD как раз про это. Его сильнейшая сторона — это совмещение скалярных(uniform) и SIMD(varying) данных в структурах и алгоритмах включая грамотный подход к обработке ветвлений/маскирований. Но SPMD — это язык в языке с кучей ограничений равно как и OpenMP/SIMD. Есть одно похожее решение — Intel SDLТ. Это полностью шаблонная С++ библиотека, но к сожалению проприетарная потому как опирается на мощь векторизатора в ICC. Она тоже про это же но только со стороны данных, ей нужен мощный векторизатор чтобы получить хороший код.

На самом деле в комитете обсуждается ряд предложений в Parallelism TS2 ровно на эти темы:
— Более низкоуровневый вариант — это P0214R3. С короткими векторами и операциями почти как в SPMD.
— Более высокоуровневый — это P0076R1 + P0075R1 + P0601R0. Алгоритмы + вызовы функций.
Все они сейчас в разной степени проработанности, близости к (моему) идеалу и продвижения к стандарту, но ни одно из них не вошло в C++17, что обидно и ровно на это я посетовал в своём комментарии.

В очередной раз попытался реализовать обработку ошибок через исключения. Поохал от кол-ва try-catch при размотке nested_exception. В связи с noexcept и неоднозначным отношением к исключениям, возникла дурацкая мысль. Что если return станет выражением? Тогда (псевдокод конечно):


std::result<int, MyIntError> obtainInt();

//...

auto obtainedInt = (auto __r = obtainInt(), __r.is_ok() ? __r.ok() : return __r.error() );
Промахнулся, пара вопросов по вашему комментарию здесь.
Поохал от кол-ва try-catch при размотке nested_exception.
А можно пример того, что вы пытались сделать и что получилось? Откуда у вас эти try-catch появляются?
Что если return станет выражением?
Вы хотите Rust-овские try! и ? в C++? В C++17 у вас будет возможность написать так (при должной реализации типа result<R,E>):
if(auto r = obtainInt(); r) {
  r.result() ...
}
else
  return r.error();
А можно пример того, что вы пытались сделать и что получилось? Откуда у вас эти try-catch появляются?

Если коротко, есть некий проект где вся обработка ошибок строится на возврате true/false либо пустого/непустого объекта. Я пытаюсь подобрать схему обработки ошибок на базе цепочки исключений, построенной через http://en.cppreference.com/w/cpp/error/nested_exception. Проблема — код для прохода по этой цепочке выглядит откровенно криво:


void ForEachNestedException(std::exception_ptr ex, FunctionRef<void (FunctionRef<void()>)> callback)
{
    while(ex != nullptr)
    {
        std::exception_ptr next = nullptr;
        try
        {
            std::rethrow_exception(ex);
        }
        catch(std::nested_exception& e)
        {
            next = e.nested_ptr();
        }
        catch(...) {} // Silence for now

        try // In case callback won't swallow exception
        {
            callback([&] () { std::rethrow_exception(ex); });
        }
        catch(...) {}
        ex = next;
    }
}

FunctionRef здесь — аналог std::function, который только ссылается на callable.
Далее пользовательский код в месте отлова будет выглядеть как-то так:


void main()
{
    try
    {
        // ... some useful code
    }
    catch(...)
    {
        ForEachNestedException(std::current_exception(), [&] (FunctionRef<void()> rethrow)
        {
            try
            {
                rethrow();
            }
            catch(ContextError& e)
            {
                // Process first type
            }
            catch(AppError& e)
            {
                // Process second type
            }
            catch(std::exception& e)
            {
                // Process all c++ exceptions
            }
            catch(...)
            {
                // Process leftovers
            }
        })
    }
}

Собственно, выходит минимум 2 throw на исключение. Пока ничего лучше я не придумал.
Альтернатива — делать свой NestedException.


Вы хотите Rust-овские try! и? в C++?

По сути да


if(auto r = obtainInt(); r) {
   r.result() ...
}
else
   return r.error();

Это будет работать как выражение? Т.е. при наличии макры


#define Try($expr) if(auto __r = ($expr); __r) { __r.result() ... } else { return r.error(); }

можно будет


auto result = Try(obtainInt()) + 42;

?


Можете поделиться ссылкой?

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

Это будет работать как выражение?
Нет, if в C++ — это statement, а не expression.

Касательно вашего примера с исключениями. Не суть понятно, зачем вам хранить всю цепочку исключений. Но создается ощущение, что если вам нужна именно вся цепочка ошибок, то в C++ без алгебраических типов и паттерн-матчинга вы все равно поимеете приключения. Даже если придумаете хитрый возвращаемый тип result<R,E>, где в качестве E будет выступать что-то, что способно хранить цепочку вложенных ошибок.

Да, это потребует эмуляции АТД. Но без short-circuit return в выражениях оно будет выглядеть убого. Довольно забавно, что в С++, при всей необязательности исключений (а иногда и невозможности), до сих пор нет идиоматичного способа обработки ошибок без их участия.

Но без short-circuit return в выражениях оно будет выглядеть убого.
Спорный вопрос. Как по мне, так Rust-овский? только усложняет чтение кода (хотя, наверное, чтение упрощает). Не хотелось бы мне в C++ видеть код вроде
result<int, Error> some_action() {
  int i = get_base()? + get_seed()?;
  ...
}
в котором обращение к get_base() и get_seed() может приводить к преждевременному возврату с каким-то значением Error.
в котором обращение к get_base() и get_seed() может приводить к преждевременному возврату с каким-то значением Error

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

В модели с исключениями вы этого не увидите вообще никак
В модели с исключениями меня, как правило, вообще не интересует, какое именно исключение вылетит и почему. Мне нужно знать всего лишь может ли вылететь исключение или нет. Если функция/метод/оператор не помечен как noexcept, то исключение вылететь может (т.е. может вылететь практически отовсюду, если только речь не идет про хардкорный embedded или жесткий real-time). Поэтому единственное, что меня заботит — это сохранение инвариантов и откат операций. А для этого в современном C++ есть достаточно средств.

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

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


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

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

Накидали — это громко сказано. Коды возврата пришли из C. Исключения появились вскоре после C++ 1.0. И именно наличие исключений сделало возможным нормальное применение перегруженных операторов. Для конца 1980-х и начала 1990-х было вполне себе нормально.

Другое дело, что сейчас модно и молодежно в АглТД и паттерн-матчинг. А вот этого в C++ нет и, к сожалению, вряд ли появится в ближайшие лет 5-6, а то и вообще вряд ли.
Накидали — это громко сказано.

К примеру, в iostreams ошибки репортятся через состояние объекта. filesystem имеет отдельный набор перегрузок, которые возвращают код ошибки. Т.е. такое впечатление, что со стратегией не определились до сих пор.


Другое дело, что сейчас модно и молодежно в АглТД и паттерн-матчинг.

Дело даже не в этом. Я показывал выше, что "раскрутка" цепочки исключений выглядит коряво. Ещё более коряво и многословно выглядит работа через result. Это при том, что С++ частенько используют в системном программировании, где исключения отключены.

К примеру, в iostreams ошибки репортятся через состояние объекта.
Корни iostreams восходят к временам, когда исключений в C++ еще не было. Кроме того, со временем в iostreams добавили возможность включить бросание исключений в случае ошибки.
filesystem имеет отдельный набор перегрузок, которые возвращают код ошибки.
А это как раз следствие того, что хотели угодить всем: и тем, кого исключения устраивают, и тем, кому исключения не нравятся.
Это при том, что С++ частенько используют в системном программировании, где исключения отключены.
Для системного программирования альтернатива в виде Rust-а появилась совсем недавно. Да и присутствие в этой нише того же Rust-а пока минимальное.

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

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


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

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

Тем более, по вашим собственным словам, его присутствие в нише системного программирования минимальное.
На данный момент так и есть, со временем ситуация может измениться. Тогда можно будет посмотреть, насколько широко в системном программировании будут применяться сложные составные Rust-овские enum-ы.
Но, опять же, единственный способ вытрясти информацию о вложенном исключении
Тут интереснее другое — зачем нужно выстраивать цепочку вложенных исключений.
Тут интереснее другое — зачем нужно выстраивать цепочку вложенных исключений.

Смотря что для вас здесь вопрос — сама цепочка или то, что она на исключениях.


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


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

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

Т.е. вы бы сделали что-то вроде


class AppException: public std::exception
{
    // ...
public:
    void addContext(ContextData);

private:
    std::exception_ptr primalCause;
    std::vector<ContextData> contexts;
};

?


Ведь в месте, где вы ловите первое исключение, у вас есть не только само исключение, но и описание контекста, в котором оно возникло (например, ID запроса, параметры сбойной операции и т.д.)

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

Возможно, я бы даже не хранил бы исходное исключение. Может быть что-то вроде:
class error_visitor;
class error_case {
public:
  virtual ~error_case();
  virtual void apply(error_visitor &) = 0;
};
class first_error_case : public error_case {
public:
  first_error_case(.../* some args*/) {...}
  virtual void apply(error_visitor & v) override { v.visit(*this); }
  ...
};
...
class error_visitor {
public :
  virtual void visit(const first_error_case &) = 0;
  virtual void visit(const second_error_case &) = 0;
  ...
};
class app_exception : public std::runtime_error {
public :
  ...
  const std::vector<std::unique_ptr<error_case>> & cases() const;
private :
  std::vector<std::unique_ptr<error_case>> cases_;
};
Потом бы в местах, где я ловлю исключения и преобразую их в app_exception делал бы что-то вроде:
void some_internal_function() {
  try {
    ... /* some actions */
  }
  catch(const first_exception_type & x) {
    throw app_exception(..., std::make_unique<first_error_case>(...));
  }
  catch(const second_exception_type &x) {
    throw app_exception(..., std::make_unique<second_error_case>(...));
  }
  ...
}
Ну и где-то на верхнем уровне, где нужно было бы с исключениями разбираться для показа их пользователю, нужно было бы сделать соответствующую реализацию error_visitor-а.

Т.е. суть в том, чтобы ловить исключение там, где его ловля актуальна и где требуется его преобразование в app_exception. Затем вытаскивание максимального количества полезной информации из самого исключения и окружающего его контекста, запихивание всей этой информации в какой-то объект (наследник error_case) и передача вверх уже этого объекта, а не имеющего конкретного типа exception_ptr.

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

Ну, визитор или не визитор — вопрос конкретного подхода и требований.
Спасибо, буду думать. С exception_ptr действительно как-то многовато мороки выходит.