Pull to refresh

Comments 330

+ из EWG прилетела бумага на переименование контрактов в snake_case.

but why?

operator != теперь по умолчанию генерируется из operator ==. Лишние операторы были выкинуты из стандартной библиотеки, некоторые были заменены на operator <=>

а это никакие optional&ltfloat&gt{NAN} не поломает?
Концепты решили переименовать в snake_case, чтобы случайно не пострадали те кодове базы, которые пишут using namespace std; и используют CamelCase наименование для собственных классов. А вообще мне тоже кажется, что CamelCase в этом месте смотрелся лучше.

optional&ltfloat&gt{NAN} поломать не должно. operator != по прежнему можно писать, и в этом случае будет использовать самописный, вместо автогенерённого.

Я так понимаю, что под CamelCase имеется ввиду PascalCase?

Теперь случайно пострадают те безумцы, которые пишут using namespace std; и используют snake_case для собственных классов :)
but why?
В стандартной библиотеке всё в snake_case. Было странно, что какая-то часть выбивалась из этого правила.

Да хрен с ними с контрактами, главное концепты оставили)


Расстроен на счет co_рутин. Что у них там вообще на уме? Теперь С++ стал еще страшнее)


Не понял за каким понадобились asm в constexpr?


Ничего не упомянули про templated lambda


Интересно, как работает внутри constexpr вектор)


Шел 2019й и НАКОНЕЦ завезли человеческое форматирование строк. Так гляди еще и ICU затащат.


В общем, приеду на конфу, закидаю вопросами)

> Не понял за каким понадобились asm в constexpr?

В основном понадобилось для ассемблерно соптимизированных алгоритмов и классов:
constexpr pow(int x, int y) {
    if (std::is_constant_evaluated()) {
        // Мы в constexpr
        --y;
        while (y--) x*=x;
        return x;
    } else {
        // runtime вычисления

        // не собиралось до недавнего времени:
        __asm__("pow eax, edx\nret"); // ассемблерный псевдокод
    }
}


> Ничего не упомянули про templated lambda

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

> Интересно, как работает внутри constexpr вектор)

Компилятор запоминает тип объекта при вызове placement new и внимательно следит за его жизнью, прерывая компиляцию при любом UB.

А вообще constexpr new пока что реализован в одном единственном компиляторе EDG, и то в developer preview.

> В общем, приеду на конфу, закидаю вопросами)

С радостью на них отвечу!)
Я кажется немного заблудился во всех изменениях стандартов. Подскажите пожалуйста — какая судьба у Networking TS? Оно куда нибудь в итоге попало?
Оно в лучшем случае попадёт в C++23. Но перед этим в C++23 должны попасть Executors, а они пока не готовы.

Насколько я помню из статьи про Feature Freeze, а С++20 это не попадает и потенциально переезжает в С++23

У вас в тексте касательно using enum несколько ошибок: KRed с большой буквы, using enum rgba_channel внутри свитча, переменная my_channel не существует.

Господи, дождались! В С++20 будет число пи :)

Меня больше завораживают констаны «квадратный корень из 3» и «квадратный корень из 2» :)

А вообще жаль что не успели принять constexpr for cmath and cstdlib. На мой взгляд второе — несколько полезнее.

Насчет корня из трех не знаю, но корень из двух попадается в формулах очень часто.
Мне больше интересно, как вся математика до сих пор жила без числа "пи", в С++11 же кучу тригонометрии и распределений насовали.

А вот так и жили. M_PI в math.h на всех распространённых платформах есть, а что в стандарте нет — ну кого это волнует.
const float pi = 3.14f; //И так сойдёт

Как-то так
А ничего не было. В стандарте его как бы нету, но в POSIX — оно как бы есть. Ну и Microsoft его себе подобрал тоже.

А что, в реальности-то, осталось, что не POSIX и не Windows?
Embedded с их огромным количеством не POSIX операционок (и отсутствием операционок), всякие ОС которые POSIX только на словах.
Так там и с поддержкой стандартной библиотеки… так себе.

Формально да — замечание верное, а практически — наличие M_PI ничуть не менее вероятно, чем какой-то фичи из стандарта…
Поэтому сейчас усиленно идёт работа над freestanding — выделением части стандартной библиотеки, которая должна работать на любом утюге. Работа идёт в паралель, так что новинки добавлять никто не запрещает.
Эмбеддер, который операционку свою написал, и число пи откуда-нибудь да скопипастит.
Вот битовые операции наконец-то привезли! Можно перестать строить свои велосипеды (лет через N-цать, в эмбед-компиляторах не сразу новые фишки появляются).
Ой. Прочитал пропозал. Вашу ж мать, а где «перевернуть биты в байте», «перевернуть байты в инте» и прочие стандартные htonl()?
Вашу ж мать, а где «перевернуть биты в байте»

а где «перевернуть биты в байте» в процессорах?
Вот тут, например:
infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/BABJJDDB.html

То, что в большинстве «больших» процессоров это придётся делать программно… Ну, прикладному программисту эти подробности в 99.9% случаев неинтересны.
Тем более, есть более интересные реализации, чем наивный цикл по 32-м битам.
Тем более, есть более интересные реализации, чем наивный цикл по 32-м битам.

да да, 25 инструкций для разворота 32 бит. Мне даже доводилось считать их в sse ибо так получалось заметно шустрее

Не, как обычные пользователи жили — понятно, а вот как без числа пи жили всякие std::sph_bessel или std::cauchy_distribution (всмысле, сам библиотечный код), если там в исходной формуле число пи?
Неужели тоже константу заводили каждый раз?

Не заводили. Прям так писали:
    // purposefully let tan arg get as close to pi/2 as it wants,
    // tan will return a finite
    return __p.a() + __p.b() *
        _VSTD::tan(3.1415926535897932384626433832795 * __gen(__g));

Если стандарт определяет функцию acos(x), то почему бы просто не написать pi = acos(1)?

Самому написать? Или библиотеке?

Один, довольно костыльный и, как мне кажется, медленный метод — acos(-1)

Говорят, 4*atan(1) точнее выходит :)

Небось умный компилятор заменял это выражение сразу на число.
А если не заменял, то это можно сразу репортить как missed optimization :)
Так и есть
int f()
{
return std::acos(-1);
}
mov eax, 3
ret
Корень из трех — это ж тангенс угла в 60 градусов :)

А битовые операции обещают константность выполнения для избегания атак по времени или оно в таком виде не рассматривается?

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

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

Так оно с законами никак не коррелирует. Это все требованя к реализации, как скажем ожидание, что доступа к элементам массива будет за O(1) времени. Так и тут. Хотя это все вероятно implementation/platform/architecture specific и криптобезопасный код будет как и раньше на чистых битовых операциях.

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

Гм. Мне одному видится отсутствие логики в принятии этого решения?


Впрочем вам конечно виднее...

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

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

Скорее всего контракты вернутся в C++23.
Контракты уже были реализованы в каком либо компиляторе?
В GCC, в отдельной ветке. Поиграться можно например на godbolt, на компиляторе gcc (contracts)
Я с вот этой веткой CLang игралсся:
github.com/arcosuc3m/clang-contracts
Делал на работе по ней презентацию с примерами, если честно, никому текущая реализация не понравилась. Очень много неочевидных моментов.
Так что я как только прочитал, что контракты выкидывают — прямо вздох облегчения :)
С++23

Началась работа над крупными вещами, включая… юникод


2019 год на дворе. Но хотя бы началась…
… но при этом до сих никто не знает как сделать эффективную поддержку юникода в C++.

Или знают но молчат :) Предложений на юникод не было с 2000. Стали появляться лишь в последние несколько лет.
> при этом до сих никто не знает как сделать эффективную поддержку юникода в C++.

Сделали бы хотя бы неэффективную, ведь есть же ICU — бери и срисовывай
Ага. А потом инсталлируй 60 библиотек по 40 мегабайт каждая.

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

Как по мне, мы здесь столкнулись с классическим примером прокрастинации, когда авторы стандарта делают не то что надо людям, а то что им хочется и интересно.
Как говорил Юсутис «Это ваша вина!».

Авторы стандарта — это обычные люди, которые просто написали предложение по улучшению языка. Так что если вам нужен юникод, а вы ничего не делали — «Это ваша вина!».
В ICU нет главного: стабильного API. И нет ни одного желающего его поддерживать, только желающие пользовать.

А так-то да, можно взять ICU за основу.
Так кто ж мешает Вам написать пропозал на основе ICU?
Уверяю, если я бы я решил писать предложения в комитет, то это было бы на первом месте. Однако гугл говорит что уже писали. Например вот предложение от 2013 года:
www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3572.html

Задача объёмная, скучная, но крайне важная, в отличие от концептов и т.п. Даже не представляю, сколько там человеко-часов надо, но точно одному не справиться.
авторы стандарта делают не то что надо людям
комитет рассматривает те предложения, которые к нему поступают от любых заинтересованных сторон. На сколько я могу судить, не нашлось никого, кто бы проработал достаточно качественное и востребованное предложение по юникоду.
UFO just landed and posted this here
Эти проблемы пока не устранили. Так что память, выделенную через new в constexpr vector в рантайм пока просто так не поместить. Надо будет vector аккуратно возвращать как std::array или как-то подругому хитрить.
UFO just landed and posted this here
надо обойти потенциальный сценарий, когда мы пытаемся в рантайме освободить статическую память. Сделать это без накладных расходов в рантайме, видимо, можно только так
UFO just landed and posted this here
Так а чем сделать некоторое поведение UB лучше чем не поддерживать такое поведение вовсе? Хочется же не только шашечки, но и ехать.
UFO just landed and posted this here
Вот переехал у нас vector из компайлтайма в рантайм. Откуда ему знать, что память в _data статическая и удалять её не надо?
UFO just landed and posted this here
именно это я и имел в виду
UFO just landed and posted this here
а почему не выделять эту память на стеке? То есть что бы компилятор вставлял объект в стек, будто он был создан в рантайме?
Не стек, а хип. А хип — это сразу какие-то сервисы от операционной системы, точно повторить которые компилятор не в состоянии…
почему хип? Если компилятор собрал объект и способен поместить его в статическую память, то почему не в стек? С хипом тоже не понял, почему компилятор не может поместить код для размещения в хипе в рантайме, также как он это делает в обычных случаях?
Я чуток подумал, и понял, что был не совсем прав. Я напрочь забыл о существовании placement new (поскольку не пользовался никогда).
Кажется, компилятору уже прямо сейчас ничего не мешает эмулировать этот placement new и класть полученный массив байтиков в .data. Проблема, как я понимаю, объяснить всем окружающим, что delete для этого объекта вызывать нельзя, он вместо освобождения памяти устроит чёрт-знает-что.

Ну и статическая память (секция .data в частности) и стек — это разные вещи. Заранее инициализированное нечто (с фиксированным адресом) — это data.

Почему компилятор не может сам разместить данные в хипе? Давайте попробую объяснить ещё раз.
Он может сделать это следующим образом: в компил-тайм просчитать содержимое объекта (выполнить конструктор) и собрать стартап-код, который при старте программы:
— вызывает менеджер памяти операционки
— копирует в полученную область заранее посчитанный массив байтиков.
Для сколь-нибудь сложных объектов (вектор — уже достаточно сложный) процедура нетривиальная, там и malloc'ов несколько, и содержимое объекта зависит от адресов, которые вернул malloc…

Disclaimer. я неплохо представляю, как работает компилятор + стандартная библиотека на baremetal, но с «большими» операционками так глубоко не погружался (работает — и ладно). Возможно, где-то выше написана лажа.
Есть легкое ощущение натягивания совы на глобус от деятельности комитета.
У меня не хватает квалификации, чтобы аргументированно критиковать. Но с моей колокольни выглядит как добавление мало нужных фич в язык, просто чтобы были.
Да я как раз за то, чтобы не трогать язык какое-то время. :)
Последнее чему радовался — введение override.
Но, повторюсь, я средненький плюсовик, поэтому моё мнение не имеет значения.
Модули, stacktrace, reflection, убрать все _ из ключевых слов. Заставить (не знаю каким образом) компиляторы выдавать сообщения обошибках в одну строку а не на пять экранов и 100 килобайт на ошибку. Избавиться от .h файлов вообще. Нормальные строки, а не нуль-терминированные. Строковую интерполяцию через $"{x}" в format. Бинарную совместимость скомпилированых модулей между компиляторами. И т.д. Простые вещи, которые реально облегчают жизнь. Накой мне constexpr, если ошибка в нем дает сообщение в 5 килобайт? Я лучше вообще не буду ими пользоваться.
Модули, stacktrace, reflection

будут

— Заставить компиляторы выдавать сообщения обошибках в одну строку а не на пять экранов и 100 килобайт на ошибку.
— Избавиться от .h файлов вообще.
— Строковую интерполяцию через $"{x}" в format.
— Бинарную совместимость скомпилированых модулей между компиляторами

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

— Нормальные строки, а не нуль-терминированные

В чем проблема нуль-терминированности строк?
image

М — Модули

Вроде выглдядит свежо. Модули уже работают с системой сборки build2 и компилятором clang. Дерзайте!

Только пишите инклуды перед объявлением модуля, а то будет вообще мясо в консоле, никогда не угадаете в чем ошибка
Почему stacktrace отклонили? Есть ли шанс увидеть его в С++23?
На него просто не хватило времени у LWG. К C++23 уж точно должен прорасти в стандарт.

Благо в Кёльне stacktrace прошёл группу Core, так что самое страшное — позади. Теперь не надо думать, как описать stacktrace в терминах абстрактной машины, у которой в принципе нет стека.
Теперь не надо думать, как описать stacktrace в терминах абстрактной машины, у которой в принципе нет стека.

Вот это мне непонятно. Когда я компилирую С++ в FPGA или ASIC там действительно нет стека, все функции либо инлайнятся, либо генерируются в отдельные модули. Но ведь например и std::filesystem на такой машине тоже нет (хотя можно реализовать).

Так почему стек не может быть опциональной фичей стандартной библиотеки, поддерживаемой на отдельных таргетах?
Может. Но даже в этом случае его надо описать в терминах стандарта (абстрактной машины без стека)
#include <type_traits>

template<typename T>
struct Optional {
    // ...
    ~Optional() requires(std::is_trivially_destructible_v<T>) = default;
    ~Optional() requires(!std::is_trivially_destructible_v<T>) {
        if (inited_) reinterpret_cast<T&>(data_).~T();
    }
private:
    bool inited_{false};
    std::aligned_storage_t<sizeof(T), alignof(T)> data_;
};

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

В отл от яз прог прив выш: где `let y: &mut i32 = &x` или `let mut parts = MaybeUninit::<[flt2dec::Part<'_>; 6]>::uninit();`
В отл от яз прог прив выш: где let y: &mut i32 = &x или let mut parts = MaybeUninit::<[flt2dec::Part<'_>; 6]>::uninit();

Свежий шедевр подобного рода (отсюда):


static UNIT: &'static &'static () = &&();

fn foo<'a, 'b, T>(_: &'a &'b (), v: &'b T) -> &'a T { v }

fn bad<'a, T>(x: &'a T) -> &'static T {
    let f: fn(&'static &'a (), &'a T) -> &'static T = foo;
    f(UNIT, x)
}
UFO just landed and posted this here
Если что – это бессмысленная отстебятина из ЛОРа. Первая строка – объявление статической переменной, являющейся ссылкой на ссылку на пустой кортеж. Остальное еще более бессмысленно, хоть и корректно синтаксически.
Именно поэтому это точно не хороший пример кода на Rust в ветке с холиваром про синтаксис, согласитесь. :)

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

с учетом того, что пример для C++ из стандартной библиотеки (куда здоровые люди обычно не смотрят--шутка), то это сопоставимые примеры

Вы специально усложнили код. Благодаря выводу типов код можно упростить до let y = &x или let mut parts = MaybeUninit::uninit();. Зачем вы набрасываете?

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

Я понятия не имею что написано в этой строчке, но сами авторы Rust считают что надо писать так. Ну и да, всё это разумеется завёрнуто в unsafe :)
То что это завернуто в unsafe намекает что эту дичь написали чтобы остальные разработчики пользовались удобными интерфейсами и в целом спали спокойно.

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

Мало кто, знаете ли, пишет на Rust стандартную библиотеку языка. :)
Я не вижу в этом проблемы. Уникальность Rust в его дуальности: по одну сторону safe Rust, гарантирующий разработчику safety, а по другую сторону unsafe Rust в котором как раз разработчик должен обеспечить выполнение всех инвариантов. За счет объединения этих двух миров под одной крышей как раз и достигаются те самые бесплатные абстракции, которые не протекают если вы подставите им неожиданный тип в шаблон или передадите не ту ссылку в соседний поток.

На счет конкретного этого примера:

let mut parts = MaybeUninit::<[flt2dec::Part<'_>; 6]>::uninit();

В действительности этот код полностью аналогичен:

let mut parts = MaybeUninit::uninit();

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

Да, этот код написан в угоду производительности (еще раз, это стандартная библиотека), и чтобы код был еще и корректным используются специфические конструкции типа MaybeUninit. Вообще если изучить стандартную библиотеку Rust она в своей реализации целиком состоит из unsafe блоков и другой черной магии. Но это точно не показатель что код на Rust именно такой. Как раз наоборот: всю отстебятину скрыли за простыми интерфейсами, чтобы обычные разработчики могли писать сложный и безопасный код пользуясь простыми интерфейсами.
Ну и «безотносительно ЯП»: бесплатные абстракции и бескомпромиссные безопасность с производительностью – это фишка Rust.

Кому-то такой подход может не подходить как раз из-за того что «такой код мейнтейнить новым разработчикам в разы сложнее отчего позже ими пишутся новые костыли и велосипеды, так как разбираться почему именно так долго не будут». Ничего страшного, для таких случаев есть масса других хороших языков, вроде Python или даже Java.

Ставка делается на то «такого кода» в вашем проекте будет от силы несколько процентов, даже если вы пишете что-то относительно низкоуровневое. Весь остальной код будет как раз таки проще мейнейнить, потому что новым разработчикам не нужно держать в голове всех ньюансов использования этого кода – ньюансы заложены в интерфейсы, за которыми спрятан unsafe.
Я не читал что вы написали по трём причинам:
* вы не вдумываетесь в то что вам пищут в ответ
* синтаксис Rust страшен до жути, отрицать это бессмысленно
* мне интересны технические споры, но (увы) ни один из спорщиков про Rust не в состоянии рассказать какие возможности по оптимизации кода даёт/забирает та или иная особенность Rust.

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

Удивите меня, расскажите не просто о своём любимом ЯП. Расскажите о том, как используя опыт и подходы из вашего любимого ЯП сделать C++ лучше.
Если вы тыкаете меня в то что решили не снисходить читать мой комментарий, почему я должен распинаться и пытаться удивить вас?

Хотите технической дисскусии – задайте технические вопросы и не опускайтесь до уровня «синтаксис Rust страшен до жути, отрицать это бессмысленно».
Почему Rust не предаёт знания об алиасинге middle endу, из-за чего генерируется менее эффективный код?

Почему у Rust такое соглашение по вызовам? Чем обоснован существующий возврат значений из функций, с большим количеством записи на стек?

Почему не стали использовать SSO строки в стандартной библиотеке Rust?

3: А зачем? Это не является вопросом уровня дизайна языка. Хочется микрооптимизаций в конкретном месте — просто добавь библиотеку: https://crates.io/crates/inlinable_string


1: Потому что это еще не успели сделать? Неужели раст настолько медленней плюсов (нет), что силы разработчиков стоит кидать на такую задачу?

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

Вот мне и интересно: почему важная оптимизация, преимущество перед многими другими языками программирования ещё не сделана? Есть технические проблемы?

Да, есть технические проблемы.


Алиасинг был сделан, но полезли баги вроде этой:
https://github.com/rust-lang/rust/issues/54462. Оказалось, что ллвм генерирует некорректный код, поэтому noalias выключили до лучших времен: https://github.com/rust-lang/rust/pull/54639. Текущее состояние: написали пример на С с restrict, который генерирует некорректный код и в кланге, и в гцц: https://github.com/rust-lang/rust/issues/54878#issuecomment-429578187


Созданы соответствующие баг репорты:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87609
https://bugs.llvm.org/show_bug.cgi?id=39282

Как говорилось, потому что раст использует &mut намного чаще, чем плюсовики restrict, поэтому когда раст начал везде расставлить noalias посыпались баги ллвм, и эту аннотацию отключили.


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

> Почему Rust не предаёт знания об алиасинге middle endу, из-за чего генерируется менее эффективный код?

Можно чуть более конкретный пример? В Rust реализуются оптимизации связанные с алиасингом или c гарантией его отсутствия.

> Почему у Rust такое соглашение по вызовам? Чем обоснован существующий возврат значений из функций, с большим количеством записи на стек?

Опять же, что имеется ввиду? Соглашение по вызовам у Rust наследуется от С++, и определятеся скорее LLVM. Или речь о возврате Result<T, E>?

> Почему не стали использовать SSO строки в стандартной библиотеке Rust?

Потому что это нарушило бы некоторые гарантии при работе со строками (отсутствие неявных копирований и перемещений при вызове некоторых методов). String – это по определению обертка над Vec, который в свою очередь является выделенной памятью на куче. Если вам нужны массивы или строки произвольной длины на стеке, есть соответсвующие крейты: smallvec и inlinable_string.
String – это по определению обертка над Vec

Ну во-первых не по определению, а во-вторых, наивная реализация строки является наименее производительной из возможных.
Во-первых, как раз по определению:
A String is made up of three components: a pointer to some bytes, a length, and a capacity. The pointer points to an internal buffer String uses to store its data. The length is the number of bytes currently stored in the buffer, and the capacity is the size of the buffer in bytes. As such, the length will always be less than or equal to the capacity. This buffer is always stored on the heap.

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

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

При этом, я сам в нескольких проектах использовал smallvec и inlinable_string, так что ничего против такого подхода не имею.
Это документация конкретной имплементации, а не определение.

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

Какие оптимизации SSO отменяет?

SSO делает более дорогими операции перемещения. Тут как раз в комментариях к недавнему посту на это жаловались.

Дайте пожалуйста ссылку на пост
Там в коментариях люди забыли добавить нетривиальный деструктор (даже без SSO надо освобождать за собой память, деструктор будет нетривиальгый). С ним разница между SSO и его отсутствием нет godbolt.org/z/jWrvsv на libc++

А вот libstdc++ чудит godbolt.org/z/z0VabY. Попробую на досуге поправить имплементацию std::basic_string.

Есть примеры где SSO вредит?
Вы про вот этот комментарий? Во-первых, два лишних mov и один xor это копейки, несравнимые с аллокаций/деаллокацией/копированием памяти в куче. Во-вторых, khim ошибся и в своем коде забыл занулить буфер из которого делается move (с настоящей строкой так делать нельзя). Вот так правильно, и… мне кажется, или я вижу одинаковый код?
Если бы вы прочитали мой комментарий до конца, то увидели бы, что я про то, почему там зануление есть — как раз отлично знаю.

А ваш код, извините, добавляет лишнее действие: разумеется условные «малые» строки (которые в принципе никогда ничего в куче не аллоцируют) при std::move зануления не требуют… Как, кстати, и libc++'ые… Кстати хорошая идея — можно попробовать его ещё чуток ускорить… Нужно только первый байт же обнулять…
UFO just landed and posted this here
Потому что так устроен C++. После того, как вы «забрали» строку в другую «объедки» должны быть валидной строкой. И, в частности, у строки должна быть возможность вызвать деструктор!

То есть по-минимуму нужно в строке из который «забрали» содержимое дёрнуть флажок, чтобы она не попыталась память освобождать, когда деструктор вызвали. А вот почему, при этом, libc++ обнуляет строку полностью — не знаю, надо будет посмотреть…
UFO just landed and posted this here
Вообще-то SSO — это «Small String Optimization» здесь. Там могут быть как короткие строки (прям внутри объекта), так и длинные (в куче).

Мы не знаем — короткая там строка или длинная. Мы просто «забираем» содержимое «как есть».

Если строка таки реально была короткая — ты мы «в шоколаде» и у нас после этого уже и так всё хорошо.

А если длинная? Тогда нужно сделать так, чтобы «старая» строка стала валидной, но на тот буфер, который она использовала раньше больше не ссылалась.

Иначе у нас окажутся две строки — и каждая будет считать, что она владеет буфером. В какой-то момент отработают два деструктора и… в общем хорошо не будет.

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

Написать им, об этом, что ли?

P.S. Альтернатива — это различать в нашем конструкторе «короткие» и «длинные» строки и обрабатывать их по разному. Но это плохая идея: даже сейчас у нас «лишних» действий — три лишних инструкции обнуляющие «старую» строку. А если сделать так, чтобы обнулялся только первый байт — вообще одна инструкция останется. А проверка плюс переход — это точно будет дороже…
UFO just landed and posted this here
Если да, то зачем там что-то забирать и флажки какие-то выставлять?

Если строка длинная, так делать нельзя. А проверка на короткость строки дороже, чем тупо занулить исходную.
разумеется условные «малые» строки (которые в принципе никогда ничего в куче не аллоцируют) при std::move зануления не требуют…

диллема в том, что в с++ неудаляющий мув, из-за чего мув и деструктор могут друг друга не видеть. А так да, в определенных случаях компилятор может зануление с деструктором оптимизировать.
Подозреваю что в тех примерах которые вы видели прирост производительности достигается за счет того что при создании пустых строк не выделяется память на хранение \0. В Rust это и так не происходит, поскольку строки не заканчиваются на \0, а на пустой вектор память не выделяется.
Пустые строки ничего динамически не аллоцировали даже без SSO, за исключением одной имплементации, которая уже больше 20 лет так не делает.

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

Смиритесь, SSO работает. По каким причинам его не внедрили по умолчанию в Rust?
Vec will never perform a «small optimization» where elements are actually stored on the stack for two reasons:

It would make it more difficult for unsafe code to correctly manipulate a Vec. The contents of a Vec wouldn't have a stable address if it were only moved, and it would be more difficult to determine if a Vec had actually allocated memory.

It would penalize the general case, incurring an additional branch on every access.

doc.rust-lang.org/std/vec/struct.Vec.html#guarantees
Подозреваю что в тех примерах которые вы видели прирост производительности достигается за счет того что при создании пустых строк не выделяется память на хранение \0.

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

Определение реализации строки в расте != определение «строки» в более общем, межъязыковом смысле. В общем случае гарантии вектора как «heap-allocated contiguous array» строке не нужны. А вот что строке нужно, так это производительность в типовых сценариях.

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

судя по бенчмаркам (некоторые привел даже автор inlinable_string), это скорее факт чем нет.

При этом, я сам в нескольких проектах использовал smallvec и inlinable_string, так что ничего против такого подхода не имею.

привет оверхеду на конвертации String <-> inlinable_string, особенно в проектах опирающихся на кучу библиотек с разным API.
Определение реализации строки в расте != определение «строки» в более общем, межъязыковом смысле.
Ух ты, готовы дать строгое и полное определение «строки» в общем, межъязыковом смысле? :)

Я согласен что в общем случае гарантии реализации Rust не нужны, но когда дело доходит до реализации вам прийдется дать какое-то определение того, что вы называете строкой, и это определение сразу же наложит ограничения на то, чем она не является. Например вы скажете, что строка – это последовательность байт, которая является валидным utf-8. Сразу же найдется кто-то, кто скажет что это неэффективно, потому что нельзя быстро переместиться к i-ому символу в строке. Если бы вы использовали utf-32, нашелся бы кто-то другой, кто сказал бы что строки занимают в 4 раза больше памяти. Сделаете оба варианта – будут жаловаться на то что нет единообразия и тратится время на конвертацию, сделаете один тип с поддержкой нескольких вариантов – будут жаловаться на накладные расходы каждый раз в тех случаях, где используется только одна кодировка.

судя по бенчмаркам (некоторые привел даже автор inlinable_string), это скорее факт чем нет.
То что показывают бенчмарки можно было узнать и без них – что маленькие строки из литералов создаются быстрее. То как это отражается на работе всех остальных методов и как это влияет на работу в условиях приближенных к реальности это не показывает. Автор сам пишет что «These are very micro benchmarks and your (hashtag) Real World workload may differ greatly».

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

привет оверхеду на конвертации String <-> inlinable_string, особенно в проектах опирающихся на кучу библиотек с разным API.
В большинстве случаев библиотеки зависят от строковых слайсов (&str, &mut str), а не от строк (String). В этом плане не важно как именно вы их создали или храните – как String или inlinable_string. При этом у inlinable_string (как и у любой реализации SSO) будут чуть большие накладные расходы на получение строкового слайса – это тоже необходимо учитывать. Если библиотека должна писать что-то, то как правило используется типаж Write, как раз потому что ответственность на выборе места, куда записывать данные лежит на пользователе библиотеки. Так что тут inlinable_string тоже не сыграет роли.
Автор сам пишет что «These are very micro benchmarks and your (hashtag) Real World workload may differ greatly».

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

Я в своих проектах не заметил

Бенчмаркали? И что в вашем понимании «статистически значимое изменение»?

В большинстве случаев библиотеки зависят от строковых слайсов (&str, &mut str), а не от строк (String)

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

При этом у inlinable_string (как и у любой реализации SSO) будут чуть большие накладные расходы на получение строкового слайса – это тоже необходимо учитывать

Вы до сих пор не понимаете что оверхед SSO (пара инструкций) на 3 десятеричных порядка меньше оверхеда от честной копии?

В расте отсутствует неявный оператор копирования строк, роэтому ситуация с честными копиями видна по расставленным .clone(), которые можно оптимизировать в &str. &str — это типа string_view, только с корректным времени жизни.

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

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

из влияющих на производительность разность подхода только одна — там, где система типов раста не гарантирует корректность, программист редко напишет unsafe ради микрооптимизации. Перед плюсовиком такая дилемма не стоит в принципе.
авторов не один, бенчмарк не один, некоторые из них взяты из реальных сервисов
Я в курсе, я тоже чуть-чуть приложился к этой библиотеке, когда она только появилась.
И когда приложение, работающее с текстом, выигрывает 10% или более от одной лишь замены реализации строки, наверно, ваша теория не верна?
Какая моя теория? Хорошо если выигрывает, пользуйтесь библиотекой на здоровье. Мы же обсуждали должно ли это быть поведением по-умолчанию для всех сервисов.
Бенчмаркали? И что в вашем понимании «статистически значимое изменение»?
Статистически значемое изменение – это если разница в результатах бенчмарков хотя бы больше стандартного отклонения последовательности измерений. В случае моей задачи это было не так, хотя вроде бы часто использовались строки маленького размера.
логично, что можно передавать слайс (что выливается в лишние аллокацию/копирование/деаллокацию если эту строку надо где-то сохранить), если копия наивно реализованной строки будет в точности такой же дорогой.
Зачем вообще что-то копировать если можно хранить строковый слайс?
Вы до сих пор не понимаете что оверхед SSO (пара инструкций) на 3 десятеричных порядка меньше оверхеда от честной копии?
Точно 3 десятичных порядка? Вы уверены? Даже на самом экзотическом микробенчмарке вы получите до 100x. В тоже время, если вы много работаете с большыми строками, «пара инструкций» оверхеда у вас появляется на буквально каждое обращение к данным в строке.

Я не собираюсь утверждать что реализация String на Rust эфективнее и лучше чем SSO, я предлагаю вам не быть так однозначно уверенным в обратном.
Зачем вообще что-то копировать если можно хранить строковый слайс?

это если лайфтайм позволяет

Даже на самом экзотическом микробенчмарке вы получите до 100x

«самый экзотический микробенчмарк» я запущу в несколько потоков, увеличивая стоимость атомиков в аллокаторе )

В тоже время, если у вас большие строки, «пара инструкций» оверхеда у вас появлется на каждое обращение к строке.

Если у меня большая строка, дополнительные 3-4 инструкции на взятие пары итераторов попросту потеряются. Чего не скажешь о короткой строке и её аллокации.
> Соглашение по вызовам у Rust наследуется от С++

Что-то непохоже. На простейших функциях Rust делает раза в три больше обращений к стеку godbolt.org/z/vzUEpJ

Почему выбран такой подход? Чем он обоснован?
Пример с алиасингом: godbolt.org/z/WsrusD

В ассемблерном коде от Rust идёт перечитывание данных по адресам (dword ptr[регистр]), в то время как код C++ с __restrict этого не делает (что намного эффективнее). Если уберёте __restrict, то увидите что код C++ и Rust совпадут один в один.
Именно это и делает изначальный пример — заворачивает дичь с микрооптимизацией optional чтобы остальные разработчики пользовались удобными интерфейсами и в целом спали спокойно.

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

~Optional() requires(!std::is_trivially_destructible_v<T>) {
   if (inited_) reinterpret_cast<T&>(data_).~T();
}

понимаю что вижу только нечитаемую вырвиглазную дичь, как и вы в Расте.
Мне вот интересно когда вы немного знали C++? В 1990м?

Тут нет ни одной синтаксической формы, которой не было в C+98 (есть requires, но он, синтаксически, вполне подобен throws, который в C++98 уже был).

Но тут все эти синтаксические формы собрались вместе.

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

Тут выше писали что Rust нечитабелен потому что вместо понятных слов используются специальные символы. Так вот С++ ни чуть не лучше в этом плане, его заслуга лишь в том что этот язык уже знает большое количество программистов.
Тильда для вызова деструктора, серьезно?
Серьёзнее некуда: деструктор именно так и описывался начиная с Turbo C++ 1.01. Может в каких-то ранних версиях «C с классами» и было по-другому, не знаю, но во всех известных мне компиляторах деструктор описывается именно так.

Это выглядит понятным только для тех, кто пользуется этим каждый день.
Эээ… Для вас «выглядит странным», что функцию можно вызвать по её имени? Вы точно на C++ хотя бы «Hello, world!» написали?

Так вот С++ ни чуть не лучше в этом плане, его заслуга лишь в том что этот язык уже знает большое количество программистов.
Ну с этим, как раз, спорить бесполезно. Как я уже сказал — мне куда больше по душе Ада… Хотя там тоже есть всякие апострофы. Похоже от идеи использовать «закорючки» разработчики ЯП никогда не откажутся.

Но в данном-то случае, как бы, речь идёт о самом центральном элементе языка, центральное некуда: C++, как известно, изначально вообще назывался «C с классами», так что классы — это центральная вещь в C++, прямо из названия понятно… а одной из важнейших «фишек» классов в C++ как раз и являются конструктор и деструктор…

Так-то да, действительно дичь… но это ж не что-то, что в C++20 появилось — это то, что в C++ было всегда
Может тильда для обозначения деструктров в С++ и была всегда, но если представить на секунду что С++ это не центр вселенной программирования, и что есть куча альтернативных языков программирования, которые выражают это иначе (можно допустить что даже лучше), то такое обозначение начнет выглядеть слегка бессмысленным. Просто представьте что вы не знаете С++ и вам нужно догадаться что делает «foo.~T()». Именно на это я и хотел намекнуть, когда говорил что забыл значение этой конструкции. (Мне стыдно, но это правда!)

Был бы там какой-нибудь foo.drop() или delete foo, такой проблемы не возникло бы. Но люди из мира С++ часто склонны считать что если что-то было всегда или давно (в С++), значит это и есть наиболее правильное положение вещей. Я даже боюсь представить что будет если сказать что отсутствие хоть какого-то ключевого слова для объявления функции или метода явно не прибавляет читабельности языку. :)
Просто представьте что вы не знаете С++ и вам нужно догадаться что делает «foo.~T()».
В этом случае, да, можно себе все мозги сломать. Но вы-то сказали, что его знаете немного — а тут уж про деструктор можно бы и вспомнить. Тем более что с этой самой тильды весь пример начался: ~Optional()… в первой строке. Там, почему-то, тильда вас не смутила.

Просто, ну не знаю… прямой вызов деструктора в C++ действительно — черезвычайно редок… Но он же, в сущности, синтаксически такой же, как вызов любой другой функции!

Ну то же самое, что с выражением Obj->*FPtr(...); — писать такое приходится редко, но я видел очень мало людей, которые бы, встретив эту конструкцию первый раз в жизни, её бы не опознали… То же самое и здесь…
Знал и конечно же реализовывал деструкторы, последний раз лет 8 назад. Такой паузы оказалось достаточно чтобы вообще не понять что передо мной вызов и объявление деструктора. Я решил что это унарный оператор и на этом мой парсер сломался.

Вы же скопировали кусок стандартной библиотеки. Мало кто пишет свою стандартную библиотеку...

Вы верно подметили, optional (с которого началась данная дискуссия) — это часть стандартной библиотеки.
ну, по-моему, это вполне читаемо и понятно. Да, требует знаний выше среднего, но «середняки» не пишут свои шаблонные либы.

Как это, неужели не я один считаю синтаксис Раста лапшеобразным?

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

Естественно я не возражаю, если вы считаете иначе.
Вы не думали, что дело только в привычке? Человеку, который много лет пишет, например, на Python, С++ будет казаться крайне вырвиглазным. Код шаблонов с использованием std::enable_if вместо указания типа и тому подобное — это же вообще кошмар. Но тем кто пишет на C++ эта магия шаблонов — нормально.

Как пример, изначально мне крайне не понравился даже синтаксис объявления функций и аргументов в Rust (я знаю что тут возвращаемый тип можно было не указывать, просто для демонстрации синтаксиса указал):
fn a_plus_b(a: i32, b: i32) -> i32
{
    a + b
}
Только чем больше времени проходит, тем больше я понимаю, что разработчики Rust не зря выбрали именно такой синтаксис объявления типа результата функции и типов аргументов и переменных, отказавшись от привычного синтаксиса C++. Тем более, что в сам C++ ввели похожую форму задания типа результата через "->", и появилось это именно из-за того, что выбранный первоначально синтаксис не везде подходит (при указании возвращаемого типа через decltype() в шаблонах, и в лямбдах). То есть в C++ есть как минимум два разных синтаксиса для объявления одного и того же, и вот это вполне себе обычный C++:
auto a_plus_b(uint32_t a, uint32_t b) -> uint32_t
{
    return a + b;
}

Впрочем, я до сих пор ещё не пишу на Rust. Так, присматриваюсь. Споткнусь на очередную вещь, которая мне не нравится, и потом жду, когда до меня наконец дойдёт, почему было сделано именно так, и тогда уже смотрю дальше =)
Вы не думали, что дело только в привычке?


«В том числе в привычке» — да. Но не «только».

Код шаблонов с использованием std::enable_if вместо указания типа и тому подобное — это же вообще кошмар


Да, и именно поэтому меня воротит от шаблонов. Впрочем я не настоящий сварщик, так что мне норм.

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


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

Тем более, что в сам C++ ввели похожую форму задания типа результата через "->", и появилось это именно из-за того, что


Из-за того, что кто-то пытается натянуть С++ на глобус, со всеми этими шаблонами и лямбдами. Строго имхо конечно.
Из-за того, что кто-то пытается натянуть С++ на глобус, со всеми этими шаблонами и лямбдами.
Если вам не хочется ничего изучать, то есть же C89, в чём вопрос?

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

Вообще основная проблема с Rust'ом — это даже не то, что он многословен (тут всегда выбор между лаконичностью и точностью описания), а вт эт вт обрз на рвн мст. Зачем превращать программу в ребус? Вроде XXI век на дворе, телевизоры с разрешением 40x25 символов уже никто для редактирования не пользует…
Если вам не хочется ничего изучать, то есть же C89, в чём вопрос?


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

Вообще основная проблема с Rust'ом — это даже не то, что он многословен (тут всегда выбор между лаконичностью и точностью описания), а вт эт вт обрз на рвн мст. Зачем превращать программу в ребус?


Ээээ, вы точно сами написали этот комментарий? Просто я как раз с этим очень согласен, да, все эти ребусы ни к чему, можно написать чуть больше слов… Но ведь вы же сами в соседней ветке меня убеждали, что если все то же самое написать многословнее, то это будет боль и беда… А теперь пишете обратное.
Речь была о том, что некоторые вещи вводятся в С++ имхо из принципа «ну давайте еще и вот это», даже если весь остальной язык при этом трещит.
Пример можно? Потому что я знаю про вещи, которые мало на что влияют (скажем возможность писать вложенные namespaces через ::), которые, действительно, могут вводиться «шоб було»… но при этом и негативного эффекта без них — мизер.

А вот разные фичи типа способа записи типа после функции — они вполне конкретные проблемы решают.

Но ведь вы же сами в соседней ветке меня убеждали, что если все то же самое написать многословнее, то это будет боль и беда…
Где? Я как раз к синтаксису rust, скорее отрицательно отношусь. Но вот идеи, которые в нём заложены — дело другое.

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


Я не говорю, что они кому-то мешают, просто… ну слишком много их.

Где? Я как раз к синтаксису rust, скорее отрицательно отношусь.


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

А синтаксис… мне может быть сколько угодно нравиться синтаксис Ады и не нравиться C++, но, увы, C++ — банально «мощнее», многие выразимые в нём концепции в Ада нереализуемы в принципе.


Ну а как же надежность ПО?! Кто будет отвечать за вот это вот все!
Ваш саркастический тролль
Ну а как же надежность ПО?!
А никак.

Кто будет отвечать за вот это вот все!
А никто. Такова жизнь. Надёжное ПО не продаётся. Смиритесь.

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

Ну помните было обсуждение, где я неосмотрительно назвал Си близким к ассемблеру языком? Вот там в итоге все дошло и до Раста.
Там Rust выступал как надёжный язык. Но ни разу, нигде я не говорил о том, что он красив и удобен.

Это разные вещи: я вполне признаю что Rust — это большой шаг вперёд в смысле надёжности (даже с учётом того, что да, у него в borrow checker'е есть ошибки), однако его синтаксис… синтаксис ужастен.

Как я уже сказал в смысле синтаксиса мне больше Ада нравится… но она ни как C++, ни, тем более, как Rust — не умеет. Увы.
Там Rust выступал как надёжный язык. Но ни разу, нигде я не говорил о том, что он красив и удобен.


Ну, наверное я вас как-то не так понял.

Что же, плюс еще одно мнение в копилку о вырвиглазности синтаксиса.

Как я уже сказал в смысле синтаксиса мне больше Ада нравится… но она ни как C++, ни, тем более, как Rust — не умеет. Увы.


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

Впрочем это все неважно, если уж люди задались целью сделать «язык для всего».
Есть те, кто считают Аду, Оберон и прочих вполне достаточными в своем текущем виде.
Вот только где они? Я таких не знаю. Людей, которые считают их красивыми — знаю (сам такой), а вот «достаточными»… нет, не видел. Давно уже не видел.

А кому-то нужны всякие приколюхи… Ну так идите в другой язык ради этого, выбор ведь огромный, и специфический инструмент обычно лучше швейцарского ножика.
Далеко не всегда, увы. А «маленькие», «красивые» языки — так и вообще редко когда оказываются достаточными. Недаром сейчас даже в embedded всё больше C++ и всё меньше C…
Недаром сейчас даже в embedded всё больше C++ и всё меньше C


По этому поводу еще раз спрошу, а то вы в прошлый раз не ответили — что вы думаете о том, чтобы в 2019 году писать на С++ 03 (то бишь 98), ну или, если более широко, вообще о концепции ограниченного использования С++ в эмбеде? Это просто вопрос, без намеков и подвоха.
UFO just landed and posted this here
Представьте себе, что это неважно — зачем и почему. Просто есть, положим, такая идея — ограничить себя НЕиспользованием каких-то новых фич. возможно это ограничение придумано даже не нами, но существует.
UFO just landed and posted this here
В принципе согласен с 0xd34df00d.

Я могу понять отказ от C и C++, как от небезопасных.

Но отказ от C++ в пользу C — понять не могу: уровень безопасности у них одинаков («никакой» в обоих случаях, к сожалению), а выразительная сила C++ гораздо выше. VLA (чуть не единственная фича C, отсутствующая в C++) в embedded вроде не особо нужны…

Используя C вы получаете всё недостатки ограниченной выразительности Ada/Rust и не получаете гибкости C++ — зачем себя так мучить?
Ну Торвальдс аргументирует (в моей интерпретации) тем, что эта выразительность не стоит той запутанности кода, которая из неё происходит при реальных применениях. С++ способствует скрытию противоречий и снижает очевидность кода более чем С, если я правильно понимаю суть аргумента Торвальдса.
UFO just landed and posted this here
Я не уверен, что аргумент Торвальдса напрямую угрожает Хаскелю, т.к. Хаскель — полная противоположность С++ в этом плане. С++ позволяет делать что угодно и поверх этого чего-угодно натягивать абстракции и сверлить в них любые дырки. Хаскель же запрещает больше чем С. Вопрос, правда, в том, удобно ли на Хаскеле написать всё что надо так, что бы компилятор мог это оптимизировать до уровня производительности С-кода.
Что такое «типокукаретиковой»? :)
UFO just landed and posted this here
уровень абстракций в хаскеле, ну, ещё повыше
а дырявят их часто?
UFO just landed and posted this here
Вы правильно понимаете аргументы Торвальдса, но забываете, что они писались больше 10 лет назад.

В C++11/14/17/20 появилось много вещей, которые могут быть полезны как раз для ситуаций, когда нужен контроль над низкоуровенвыми аспектами. Но с его мнением по поводу STL и Boost я, пожалуй, соглашусь: уже даже банальный std::string может оказаться слишком тяжёлым для embedded. Но вот, например, сопрограммы — могут позволить сделать некоторые вещи куда как более надёжным и эффективным способом, чем в C++. Хотя за ними нужно следить, так как они, увы, могут аллоцировать память — а в embedded это не рекомендуется.
В C++11/14/17/20 появилось много вещей, которые могут быть полезны как раз для ситуаций, когда нужен контроль над низкоуровенвыми аспектами


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

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

Но как раз вещи, появившиеся в C++11/14/17 — зачастую позволяют сделать код меньше и не требуют тяжёлых поддерживающих библиотек. Это существенно изменяет баланс по отношению к C++98.
В C++11/14/17/20 появилось много вещей, которые могут быть полезны как раз для ситуаций, когда нужен контроль над низкоуровенвыми аспектами.
Но старые практики, которые ведут к непонятному или обманчивому когду, всё равно не получается запретить. К сожалению.
вещи, появившиеся в C++11/14/17 — зачастую позволяют сделать код меньше
это добавляет преимуществ (которые и так есть), но не устраняет недостатков [по сравнению с С].
за ними нужно следить, так как они, увы, могут аллоцировать память
а это вообще реально на практике? Предполагается какой-то ключ типа "--couroutine-never-allocate"?
Но старые практики, которые ведут к непонятному или обманчивому когду, всё равно не получается запретить. К сожалению.
Ну тут ничего не поделать, увы.

а это вообще реально на практике? Предполагается какой-то ключ типа "--couroutine-never-allocate"?
Насколько я знаю нет. Но там используется указанный вам operator new, можно его не описать, но нигде не определять — тогда ликер это сможет отловить… но это будет зависеть от уровня оптимизации…
С++ способствует скрытию противоречий в коде более чем С


Да, причем не только Торвальдс об этом говорит.
Используя C вы получаете всё недостатки ограниченной выразительности Ada/Rust и не получаете гибкости C++ — зачем себя так мучить?


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

Скажем возьмите то же метапрограммирование. Чтобы не сойти с ума в C++98 вам придётся использовать MPL, в C++11 — Hana, а в C++17… всё необходимое — есть в стандартной библиотеке и многие полезные вещи делаются гораздо проще, чем в C++98… даже с использованием MPL или Hana
А вот это как раз — очень плохая идея. Вещи, добавляемые в стандарт не из пальца высасываются.


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

Собственно и упоминание C++03 не просто так — это база для MISRA C++2008. Это пока не выступает ограничителем в нашем частном случае, но может.
Вы же поняли, что я не просто так спрашиваю, а из серии show your practices.
А они просты: включаем -std=c++17 в сборке, но запрещаем все фичи в Style Guide. Потом добавляем потихоньку те, которые полезнее всего (самыми полезными оказались лямбды в нашем случае — просто-нипросто гораздо удобнее делать callback'и).

Пока фичу нельзя продемонстрировать в конкретном месте в конкретном случае — никому знать как она работает и не нужно, а когда потребуется — так есть несколько CLей, показывающих «как надо».
А они просты: включаем -std=c++17 в сборке, но запрещаем все фичи в Style Guide. Потом добавляем потихоньку те, которые полезнее всего


Отлично, привожу пример — понадобились только std::array (и то я хз, какова будет его ценность при отключенных прерываниях, разве что совсем уж глупые ошибки в компилтайме ловить), неймспейсы и классы (просто классы, любим инкапсуляцию и синтаксический сахар в виде конструкторов). Ииии… и что, это «modern C++»?
А вам надо, чтобы был «modern C++»… или чтобы работало?

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

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

Главное — не пытайтесь использовать фичу потому, что она «модная». То же метапрограммирование — отличная вещь, но… Если вместо пяти функций на пять строк вы напишите одну на сто… оно вам надо? А вот если одну функцию на двести строк вместо сотни функций по три строки — другое дело же!
Да ну нет, я-то естественно не собираюсь гнаться за новыми фичами, это для вас должно быть уже немного очевидно. Вопрос был о том, приемлемо ли не гнаться, и где проходит тот водораздел между можно и необходимо. Простейший пример — как понять, когда std::array вместо С-массива уже нужен, а когда еще нет? Если ответ на это для вас окажется простым, то вот посложнее — как понять, что вам реально нужен std::vector, а не что угодно другое вместо него? Понятно, что в обоих случаях мы говорим о ситуации отсутствия внешних ограничений, выбирать можно что угодно, мне просто интересно, что вы об этом думаете.

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

Выбор же структур данных — это вообще не наука, а искусство, если бы на него можно было в двух абзацах ответить, то не получали бы программисты вдвое-втрое больше, чем токари и сварщики…

P.S. Начните с мелких фич — причём самых новых. Скажем с constexpr и static_assert: вы можете использовать constexpr-переменные и if constexpr чтобы гарантировать, что какие-то вещи вычислятся при сборке прошивки, а не будут повторно вычисляться 100500 раз. А заодно — часть вещей из прошивки просто уйдут. Полезно? Да. Сложно? Ну… отчасти: когда попробуете это сделать, то обнаружите «много интересного» в том коде, про который вы, вроде бы, всё понимали. Причём «интересного» в стиле «кто придумал сделать так, что вот это вот выражение, которое можно, вроде бы, посчитать статически, мы вычисляем 100500 раз в секунду и зря греем воздух»…
А если вы пишите код и/или делаете рефакторинг — ну вот тогда и нужно вспомнить Мейрса и подумать — а нельзя ли тут написать проще, используя возможности C++


Естественно вопрос именно об этой ситуации, когда планируется вообще новый проект (небольшой) и обсуждается спектр решений: С99 (как было), С++03 (если вдруг придется переносить в продуктовый контент), либо взять и забабахать С++14 например.

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


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

ы можете использовать constexpr-переменные и if constexpr чтобы гарантировать, что какие-то вещи вычислятся при сборке прошивки, а не будут повторно вычисляться 100500 раз


А оно мне надо? Ну, серьезно, не стоит так уж рассуждать об эмбеде, будто там все те же 128 байт ОЗУ и 1к ПЗУ, и это повсюду. Ничего, я потерплю лишние вычисления… Впрочем разумеется вы правы, это вещь полезная, но как пример акцептации — так себе.

static_assert


На правах троллинга: есть же _Static_assert!
Естественно вопрос именно об этой ситуации, когда планируется вообще новый проект (небольшой) и обсуждается спектр решений: С99 (как было), С++03 (если вдруг придется переносить в продуктовый контент), либо взять и забабахать С++14 например.

Шел 2019 год…

Поверьте, лучше переводить проекты на самые современные стандартны по мере выхода последних, чем мамонтокодить из-за недоступности фич последнего стандарта.
Простейший пример — как понять, когда std::array вместо С-массива уже нужен, а когда еще нет?

Если речь про новый код на C++11 или новее, то тут уместнее обратный вопрос: каковы причины использовать C-шный вектор вместо std::array?


как понять, что вам реально нужен std::vector, а не что угодно другое вместо него?

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


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

Если речь про новый код на C++11 или новее, то тут уместнее обратный вопрос: каковы причины использовать C-шный вектор вместо std::array?


Вопрос справедливый, но ведь… Ведь использование С-массивов не запрещено… Вопрос же в этом — как выбрать между "все новые фичи — в будущий релиз!" и "используем по необходимости"? Если необходимость эту никто толком не может обосновать.

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


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

Пожалуй дискуссию стоит завершить ввиду ее полного перехода в философскую плоскость.

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


Почему такое многообразие средств решения повседневных задач не вызывает сожалений, а наличие разных возможностей в ЯП вызывает?


Но раз философский, то философский. Но вот это хотелось бы уточнить:


ведь и накладных почти нет

Под "почти" что подразумевается?

Почему такое многообразие средств решения повседневных задач не вызывает сожалений, а наличие разных возможностей в ЯП вызывает?


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

Под «почти» что подразумевается?


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

Так если вы посмотрите на другие профессии, то там так же прогресс и необходимость учиться новому. Где-то в меньшей степени, где-то в большей. Вас же устраивает, что врачи вынуждены постоянно повышать свою квалификацию, осваивать новые лечебные методики, новые лекарства и т.д. Почему с программированием должно быть наоборот?


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

Прогресс неумолим. Нельзя закуклится и прожить остаток жизни в коконе. Даже если очень хочется.


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

Через operator[]() в релизной сборке — нет.

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


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

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

Через operator[]() в релизной сборке — нет


Гм, а зачем он мне, если я использую array? Или наоборот — если я не использую at(), то на кой черт мне array?
Или наоборот — если я не использую at(), то на кой черт мне array?
Вообще-то прямо на страничке с описанием написано: The struct combines the performance and accessibility of a C-style array with the benefits of a standard container, such as knowing its own size, supporting assignment, random access iterators, etc. An array can also be used as a tuple of N elements of the same type.

Основное достоинство std::array — это то, что это контейнер. Может быть передан в любой алгоритм, работающий с контейнерами, вы можете его передать в функцию и вернуть из неё (по значению, не по ссылке) и так далее.

А нужно ли вам это… ну, в принципе это вам как бы решать.
Так, стоп, вы то ли не читали меня, то ли не поняли. Очевидно же, что если я веду разговор о сравнении С-массивов и std::array, то я ни в коей мере не говорю о возможностях использования последнего в качестве стандартного контейнера, потому что С-массив в таком качестве использован быть не может, а значит сравнение не имеет смысла. Речь идет о ситуации, когда мне просто нужен массив, который ни в какие функции стандартной библиотеки я передавать не собираюсь (потому что если бы собирался, то выбор был бы сделан сам собой).
UFO just landed and posted this here
Интересное дело, а где я утверждал обратное?

Речь не про обратное, а про стенания вроде:


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

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


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

Когда tuple, optional и variant появились в Boost-е, а на github-е появилась мало кому известная тогда fmtlib, это и были эксперименты на пациентах. Теперь это, можно сказать, официально утвержденные методики "лечения". Со своими побочными эффектами, конечно же, знание которых является вашей обязанностью.


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

Это ортогональные вещи и одно не должно препятствовать другому.


Или наоборот — если я не использую at(), то на кой черт мне array?

Потому что у array есть size() и value_type, например. Потому, что array можно передать в шаблонную функцию, написанную во времена C++98 вместо vector-а, например. Потому, что при использовании С-шных массивов, variadic templates, initializer lists и perfect forwarding могут быть интересные сюрпризы. Тогда как с array все куда как предсказуемее.

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


Это очевидно. Но не следует эту фразу использовать не к месту.

Это ортогональные вещи и одно не должно препятствовать другому.


Они не то чтобы ортогональные, но действительно не об одном и том же. А вот время — общий и ограниченный ресурс.

Теперь это, можно сказать, официально утвержденные методики «лечения»


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

Потому что у array есть size()


Да, еще одна полезная фишка, но чуть менее полезная, чем at().

Потому, что array можно передать в шаблонную функцию, написанную во времена C++98 вместо vector-а, например


Да да да, а С-массив нельзя, и это, блин, очевидно, поэтому не является предметом сравнения С-массива и std::array, я об этом ниже написал.
Но не следует эту фразу использовать не к месту.

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


А вот время — общий и ограниченный ресурс.

И вы хотите, чтобы ЯП как-то заботился о вашем времени? Странное желание. ЯП — это инструмент, вы его выбираете. Не нравится тратить время на изучение C++, возьмите что-то другое.


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


Угу, вроде Арбидола.

От ошибок никто и нигде не застрахован. В том же C++ были спецификации исключений, экспорт шаблонов и auto_ptr. Признаны ошибками развития и изъяты из языка.


Да, еще одна полезная фишка, но чуть менее полезная, чем at().

Простите, я не понимаю, вы здесь специально тупите или просто откровенно демонстрируете свой уровень понимания C++? Вам перечислили целый ряд достоинств array перед С-шным массивом (и что-то еще вообще явно не обговаривали). Никаких контраргументов в пользу С-шных массивов нет. Ну вот просто нет.


И в этих условиях вы упорно продолжаете задаваться вопросом почему array предпочтительнее C-шного массива? Вы серьезно?

Простите, я не понимаю, вы здесь специально тупите или просто откровенно демонстрируете свой уровень понимания C++?


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

Вам перечислили целый ряд достоинств array перед С-шным массивом


Я вот тоже не пойму, вы «специально тупите», или что? Я же русским языком написал, что сравнивать «целый ряд достоинств» с попросту отсутствием аналогичного функционала у С-массива просто бессмысленно. Я с самого начала говорил лишь о том, что вообще имеет смысл сравнивать. Была условная программа на «Си с классами», в которой использовались простые массивы, потом было решено исправить некоторые проблемы за счет средств языка (как советовал нам khim), и вот мы такие взяли и поменяли «просто массив» на std::array, не меняя принципиально ничего больше, понимаете? Никаких итераторов, как работали простой индексацией элементов — так и работаем. И вот мы видим, ага — можно использовать size (вместо сами знаете чего), это относительно прикольно, а еще можно использовать at (вместо оператора), это вообще интересно (хотя конечно khim прав, что это так себе защита, и тем не менее). Что тут непонятного-то?
Что тут непонятного-то?

Непонятно что вы хотите донести до своих собеседников.


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


Поэтому у вас есть выбор: либо вы следите за эволюцией языка и своевременно получаете выгоду от его новых плюшек, либо используете старый багаж знаний и закатываете Солнце вручную там, где люди спокойно применяют variadic templates, tuples и structured bindings.


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


И вроде как вы сами все это понимаете, но почему-то задаете вопросы из категории "а зачем все это нужно?"


Что для примера с array, то вот это:


Была условная программа на «Си с классами», в которой использовались простые массивы, потом было решено исправить некоторые проблемы за счет средств языка (как советовал нам khim), и вот мы такие взяли и поменяли «просто массив» на std::array, не меняя принципиально ничего больше, понимаете?

Вызывает у меня серьезное непонимание. Если у вас есть работающая и отлаженная кодовая база, то там нет смысла просто так что-то менять. Работает с С-шными массивами код, который был написан в 1992-м? Ну и пусть работает.


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


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

А вот если вы пишете новый код


Мой старый вопрос к khim, выплеснувшийся и сюда, состоял в том, как грамотно провести работу по переводу части команды с написания кода на Си к написанию кода на С++. Исходный посыл состоял в его аргументах в большей надежности последнего (хотя по его мнению ).
> как грамотно провести работу по переводу части команды с написания кода на Си к написанию кода на С++

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

— либо человек осознает угребищность языка C и его несоответствие нынешним реалиям, и пишет на С только по необходимости (ничего другого нет на целевой платформе). Тогда его достаточно просто снабдить более современным и продвинутым инструментом. Хоть C++, хоть Rust, хоть Ada. Хоть еще что-нибудь, что не застряло в конце 1960-х;
— либо он упоротый C-шник, напрочь отвергающий все, что не C. Тогда только ~~карательная медицина вперемешку с показательными расстрелами~~ смена состава на проекте. Ибо настоящий программист, как известно, напишет программу на Фортране на любом языке программирования.
Это какой жизнью такой подход подтверждается, если не секрет? Вы сами пробовали вот так вот «гнать Васянов на мороз» и потом объяснять холдерам это вот, про застревание в 60-х?

И, к слову, опять встает вопрос — под современным инструментом понимается любой С++, или непременно последний?
Это какой жизнью такой подход подтверждается, если не секрет?

Моей.


Вы сами пробовали вот так вот «гнать Васянов на мороз» и потом объяснять холдерам это вот, про застревание в 60-х?

Гнать не пробовал. Пробовал отфутболивать при приеме. Получается замечательно.


И, к слову, опять встает вопрос — под современным инструментом понимается любой С++, или непременно последний?

В сравнении с чистым C, без каких-либо GNU-тых расширений, даже C++ времен 1994-1995 годов был большим шагом вперед. Невероятно большим.


По нынешним временам, с компиляторами уровня GNU 4.8 или даже MSVS2013 жить вполне можно. Но даже после полугода более-менее плотной работы с C++17 на C++11 уже возвращаться не хочется.


PS. К embedded разработке отношения не имею. Мне достаточно того C-шого гуано уровнем повыше, с которым время от времени приходится иметь дело.

UFO just landed and posted this here
UFO just landed and posted this here
Продолжать, надеюсь, не надо.

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

UFO just landed and posted this here
Вы ни разу не сталкивались с вылезающими провялениями UB при апгрейде компилятора?

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


Теперь про это интересно послушать уже мне.

Да нет, это вы продолжайте. У меня, например, и в мыслях не было говорить про случай, когда вы в 2019-ом получили .obj файл свеженьким MSVC++ и попытались слинковать его с .lib-файлами из 1992-года.


А если говорить про исходники, то при чем здесь ABI?

UFO just landed and posted this here
Но, если вы внимательно посмотрите по сторонам, то увидите, что что-то другое, если оно не умерло или не стало Cobol-ом, так же развивается.
«Не стало Cobol-рм» исключите, пожалуйста. И в COBOL-2014 и в Fortran 2018 (и в обоиз случаях это название стандарта, да), появились новые, достаточно нетривиальные, вещи.

Да, Cobol несколько отстаёт, но, уверяю вас, появление динамической аллокации памяти или рекурсии (в XXI веке, да) — не менее сильный удар для разработчиков на Cobol, чем метапрограммирование для разработчиков на C++.

Вам перечислили целый ряд достоинств array перед С-шным массивом (и что-то еще вообще явно не обговаривали). Никаких контраргументов в пользу С-шных массивов нет. Ну вот просто нет.
Есть один. «Знатокам предметной области» его не нужно изучать.

И тут есть ровно два варианта: либо как-то объяснить им, что на одном лишь «знании предметной области» до пенсии протянуть нельзя (посмотрите на Nokia и всех их «знатоков предметной области»… а они ведь лидером рынка были до 2010го года), либо, если это невозможно — оставить их «дорабатывать до пенсии» и устроится в компанию, где вам будет интереснее, и откуда вы, если потребуется, сможете уйти в другой место с меньшей «кровью».
«Не стало Cobol-рм» исключите, пожалуйста.

Cobol был упомянут не потому, что он не развивается, а потому, что его развитие мало на что влияет. Вот тот же Fortran по-прежнему применяется в ВУЗах, где занимаются серьезными математическими/физическими/химическими расчетами. Так что многие студенты хотя бы хоть как-то с Fortran-ом соприкасаются.


В отличии от Cobol-а.

Мммм, а проверка границ для переменного индекса разве не создает накладных?
Если вы её заказываете — то создаёт. Но operator[] проверок не делает, а at нужно вызывать явно.
Процитирую сам себя:
если я не использую at(), то на кой черт мне array
Если вы считаете, что std::array существует ради проверок границ — то вы неверно выбрали язык. Вам Ада нужна или Rust. А в C++ существование at в std:array — это сильно-сильно побочное свойство, далеко не всегда нужное. Его смысл — совсем в другом.

Собственно я даже не могу вспомнить ни одного проекта, где бы std:array использовался ради at
Если вы считаете, что std::array существует ради проверок границ


Мы только что говорили о мире пони и мармелада волшебного программирования, в котором используется только то, что нужно, и ничего лишнего. Если я решил заменить С-массив, то очевидно от std::array мне нужно только size (и то не всегда) и at(). Иначе смысл теряется совершенно.
Если я решил заменить С-массив, то очевидно от std::array мне нужно только size (и то не всегда) и at(). Иначе смысл теряется совершенно.
Совершенно не факт. В моей практике возможность передавать [небольшие] массивы по значению (а не копировать их с помощью memcpy) и возвращать их из функции — оказывается куда полезнее at. Даже в простых программах близких к «C с классами».

А вот как раз at — оказывается бесполезен: он, типа, «даёт дополнительную защиту», но требует exception'ов и вообще выглядит пятым колесом в телеге. Ибо хоть с at, хоть без at — способов «накосячить» так, чтобы компилятор морчал и в рантайм вы тоже никаких внятных сообщений об ошибках не получили в C и/или C++ — десятки (хорошо если не сотни).
Ну, ммм, на мой взгляд возврат массива из функции выглядит немного экстравагантно для эмбеда (если это именно возврат вновь создаваемого массива, а не передача владения). Если с памятью нет ограничений — то конечно норм.

А при чем тут память? Массив же может быть небольшой длины, 3 там или даже 1...

Даже если массив большой длины, но ваша функция его создаёт — то вернуть std::array в C++17 это самый эффективный метод.

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

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

Вот если нужно возвращать несколько массивов — то тут всё уже не так просто… но эта задача встречается гораздо реже.
А функция этот массив где создает? Пусть он хоть полбайта.

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

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

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

А откуда вообще взялась идея, что память для std::array в принципе может быть неявно выделена на куче без вашей на то команды?


Части ли вы сталкивались с внезапным выделением памяти на куче?

Ну, эээ, вообще-то std::array на куче никак не может быть выделен, он создается как обычный массив. Если в области видимости функции — то на стеке.

Мое возражение против возврата массивов было обусловлено тем, как это было бы сделано на Си например. В том случае, если хочется создать массив и передать его вызывающей функции, то наиболее очевидный способ — динамический массив на куче.
В том случае, если хочется создать массив и передать его вызывающей функции, то наиболее очевидный способ — динамический массив на куче.
Не совсем. Есть ещё вариант: создать массив, не инициализировать его и передать его адрес в функцию, которая его «создаст».

И, сюрприз-сюрприз — ровно это и произойдёт, если вы вернёте достаточно большой std::array из функции в C++17 (на самом-то деле и в C++11/14 тоже, но в C++17 это гарантируется). Сравните код foo и bar.
Не совсем. Есть ещё вариант: создать массив, не инициализировать его и передать его адрес в функцию, которая его «создаст».


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

И, сюрприз-сюрприз — ровно это и произойдёт, если вы вернёте достаточно большой std::array из функции в C++17


Это я уже понял. Красивое и мощное колдунство, жаль, что не очевидное из кода (пока не прочтешь очередные сотни документации).
если это именно возврат вновь создаваемого массива, а не передача владения
Читать про copy elision. Много думать. Особое внимание обратить на «guaranteed copy elision» и RVO в C++17.
Мда, оригинальный подход. Задачу решает конечно, но печально, что все это делается тайно. Уж лучше бы я сам память выделил где нужно.

Нытье, не обращайте внимания.
Если необходимость эту никто толком не может обосновать.
Почему не могут — могут. В Guidelines и Style Guideах всё обосновано.

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

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

Мы тоже вектор используем — и я могу объяснить почему: объём данных для обработки мы узнаём только в райнтайм, в статический массив данные не влезут, теоретически там гигабайты могут быть… а как у вас?

А вот там, где теоретический объём у нас ограничен — там как раз std::vector'а у нас нету. Есть std::array, ссылающийся на std::array — опять-таки, потому что так устроена предметная область.

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


Уже читал. Но в целом занятно вы пишете — вот вам гайд, но вы его прямо так не берите… Редкая по мощи определенность.

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


Я и написал выше, что в открытом вот прямо сейчас на экране проекте нет нужды в векторах. А вот был проект, где были (по причинам, сходным с указанными у вас) самописные связные списки, и ни одного аргумента что-то в этом менять. Понимаете теперь?
Но в целом занятно вы пишете — вот вам гайд, но вы его прямо так не берите… Редкая по мощи определенность.
Самое ценное в этих гайдах — это «Pros» и «Cons». Вот они — объективны. А вот «Decision» — это конкретное решение, которое конкретные люди в конкретных условиях приняли. У вас веса утверждений из первых двух пунктов могут быть другими — и «Decision» тоже будет другим.

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


Ровно этот вопрос я и задавал, спасибо. Хотя пока не пойму, как мне согласиться со второй частью вашего утверждения. Усложнение языка автоматически означает усложнение понятности программы, хотя может и формально уменьшать количество операторов.
Усложнение языка автоматически означает усложнение понятности программы, хотя может и формально уменьшать количество операторов.
Совершенно необязательно. Давайте на примере. Что-нибудь достаточно искусственное… ну, например, divmod — функция, которая сразу считает делимое и остаток.

Начнём с C++20:
// Описываем.
auto divmod(auto x, auto y) {
    return std::tuple{x / y, x % y};
}

// Используем.
auto [div, mod] = divmod(x, y);
Всё просто и понятно.

Теперь C++17:
// Описываем.
template <typename T1, typename T2>
auto divmod(T1 x, T2 y) {
    return std::tuple{x / y, x % y};
}

// Используем.
auto [div, mod] = divmod(x, y);
Описание стало несколько более запутанным.

Следующий шаг, С++14.
// Описываем.
template <typename T1, typename T2>
auto divmod(T1 x, T2 y) {
    using T = std::common_type_t<T1, T2>;
    return std::tuple<T, T>(x / y, x % y);
}

// Используем.
int div, mod;
std::tie(div, mod) = divmod(x, y);
Описание превратилось в «птичий язык», при использовании — типы нужно указывать явно (можно и вывести, но синтаксис там такой, что глазки вытекать начнут).

Следующий шаг, C++11:
// Описываем.
template <typename T1, typename T2>
std::tuple<
    typename std::common_type<T1, T2>::type,
    typename std::common_type<T1, T2>::type>
divmod(T1 x, T2 y)
{
    using T =
      typename std::common_type<T1, T2>::type;
    return std::tuple<T, T>(x / y, x % y);
}

// Используем.
int div, mod;
std::tie(div, mod) = divmod(x, y);
Это уже на выставку ужасов можно отравлять. Первого места, пожалуй, не займём… но призовое можем.

Наконец «старый добрый» C++98:
// Описываем.
template <typename T>
std::pair<T, T> divmod(T x, T y) {
    return std::pair<T, T>(x / y, x % y);
}

// Используем.
std::pair<int, int> result = divmod(
    static_cast<int>(x), static_cast<int>(y));
int div = result.first;
int mod = result.second;
Описание стало в чём-то даже проще, но вот использование стало весьма хитрым: нам нужно самим, руками, определить — каким будет тип результата, преобразовать в него (иначе тип аргумента не выведется) и так далее.

По-моему хорошо видно, что чем дальше мы уходим от современного C++, тем проще становится язык и тем сложнее становится код. Или нет?

Вроде для C++11 можно компактнее:


template <typename T1, typename T2,
   typename T3=typename std::common_type<T1, T2>::type>
std::tuple<T3, T3>
divmod(T1 x, T2 y)
{
    return std::tuple<T3, T3>(x / y, x % y);
}
Согласен — так даже гибче. Всё равно это довольно-таки замороченно и гораздо сложнее C++20 версии.
Во-первых хочу сказать спасибо за приложенные усилия, тем более что пример получился блестящий.

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

1) С++20. Несмотря на сами-знаете-какое понимание мной этого стандарта, я этот код понял! Правда на С++ выглядит уже совсем мало похожим.

2) С++17/14/11. Угар и содомия, не уверен, что все написанное понял.

3) С++98. Не поверите — все понял! Описание реально простое, а использование… Заметно длиннее, чем у С++20, да, но зато нет нелюбимого мной auto. Сильнее всего портит малину кастование конечно, если еще и его убрать — вообще красота будет.

То есть маргиналу типа меня надо было уснуть в 1998 и проснуться в 2020 (правда пришлось бы принять вещества, чтобы перестроить мозг на вышеприведенную нечеткость и легковесность).
Ну вот как-то так. Я бы, сказал, впрочем, что вариант C++17 не сложнее, чем вариант C++98 (хотя сложнее, чем вариант C++20, конечно).

Собственно потому я и не рекомендую брать C++11 — в нём появилась масса очень полезных новых возможностей, но удобный синтаксис для них — это уже C++17/20. Только C++17 уже сейчас есть, а C++20 — нет (но все активные разработчики компиляторов за ним следят и есть ощущение, что после выхода будут готовы его поддерживать буквально через год-два).
В случае с С++17 просто получилось странное смешение стилей. Типы аргументов все еще требуется указывать, но возвращаемое значение уже auto. Впрочем соглашусь, что вариант на С++17 много-много лучше С++11.
Следующий шаг, C++11:

Можно так:
template <
    typename T1,
    typename T2,
    typename T3=typename std::common_type<T1,T2>::type>
divmod(T1 x, T2 y)
{
    return {x / y, x % y};
}

Нет, ну std::tuple же где-то должен быть. Выше уже писали — но в том случае я бы привёл уж x и y к T3, перед тем, как их делить.

P.S. Можно, наверное, этот комментарий чуть раскрыть и в статью превратить — просто действительно получилась очень наглядная иллюстрация ко всему процессу эволюции C++. Переход с C++98 на C++11 был очень болезненный в том смысле, что появились новые полезные фичи — но синтаксис стал, зачастую, более громоздким, чем в C++98. Все дальнейшие стандарты развивались в двух направлениях — с одной стороны появлялись новые вещи, которые в старых стандартах сделать невозможно, но, пожалуй, даже более важным является то, что в C++14/17/20 вещи, которые в C++11 уже возможны — выражаются гораздо короче и ими реально хочется пользоваться (а не как в C++11, где их стараешься избегать и используешь только если уж совсем «прижало»).
Здравствуйте, большое спасибо за пример. А как называется данная конструкция, чтобы почитать про нее:
auto [div, mod]
UFO just landed and posted this here
Лично я не вижу никаких причин не использовать новые фичи, а про закостенелость команды разработчиков постараюсь узнать на собеседовании и сделать выводы о том, принимать оффер или нет


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

Вот это враньё. пруф.

auto a_plus_b(uint32_t a, uint32_t b) -> uint32_t

Такое писать ненужно. В С++ есть вывод типа возврата. В ситуации же с растом:

fn a_plus_b(a: i32, b: i32) -> i32

-> i32 — это писать нужно. Потому что вывода типов нету.

То есть в C++ есть как минимум два разных синтаксиса

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

auto a_plus_b(uint32_t a, uint32_t b) {
  return a + b;
}
auto a_plus_b(uint32_t a, uint32_t b) -> uint32_t {
  return a + b;
}

В какой вселенной это два синтаксиса? Явно не в этой. По аналогии let x; и let x: T; тоже два разных синтаксиса.

Человеку, который много лет пишет, например, на Python, С++ будет казаться крайне вырвиглазным.

В этом нет никаких оснований. Он не будет казаться вырвиглазным — он будет касаться избыточным, но у С++ синтаксис не является избыточным. Аннотировать типы нужно? Аннотация в С++ куда менее избыточна чем в раст и куда более мощная(это даже сравнивать никак нельзя). Никак это не убрать. Точно так же наличие ссылок/указателей и прочего — это всё требует синтаксиса. От этого не уйти ни в каком языке.

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

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

Точно так же fn и прочая паскалятинка — это всё обусловлено слабость реализации. Это всё синтаксический мусор.

разработчики Rust не зря выбрали именно такой синтаксис объявления типа результата функции и типов аргументов

Что за чушь? Они ничего не выбирали. В примитивном синтаксисе вначале декларации всегда стоит кейворд. Это никак нельзя обойти. Никакого там выбора не было.

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

Ах да, все эти рассуждения ломаются об лямбды. К тому же, в расте нету никаких decltype/typeof — потому что язык попросту слаб. Толку там с этого ровно ноль. Хотя нет, толк есть. Вывода всё равно нету, а значит приходится указывать тип из аргументов. Хотя нет, мы не можем указывать тип аргументов — мы можем указывать тип генериков. Ой, а шаблоны в С++ пишутся до типов и опять факап.

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

Без какого-то минимального понимания, минимальной аргументации пытаться понимать мотивацию языков.
Манипуляция, а вернее попросту неправда.
Просто ошибка. Я эту приписку в скобках сделал уже после написания комментария, и вместо того, чтобы приписать его к примеру на C++, приписал его к первому примеру.
С чего вдруг они «два разных», если это один синтаксис?
Два разных. Вот это:
auto a_plus_b(uint32_t a, uint32_t b) -> uint32_t {
  return a + b;
}
и вот это
uint32_t a_plus_b(uint32_t a, uint32_t b) {
  return a + b;
}
объявляют одно и то же. Два разных синтаксиса для указания типа возвращаемого значения.
Что за чушь? Они ничего не выбирали. В примитивном синтаксисе вначале декларации всегда стоит кейворд. Это никак нельзя обойти.
И это замечательно, что перед каждым объявлением переменной есть специальное ключевое слово. У сишного варианта объявления переменных синтаксис неоднозначный. То что его сложнее парсить не делает его лучше. Из-за этого, например, если мы хотим вызвать дефолтный конструктор (что без параметров) у объявляемой переменной, мы ни в коем случае не должны ставить пустые круглые скобки — компилятор запутается, и подумает что это объявление функции.
UFO just landed and posted this here
[[nodiscard]] теперь можно навешивать на конструкторы и целые классы

Не совсем понятно, каким боком тут «целые классы»:

«The attribute-token nodiscard may be applied to the declarator-id in a function declaration or to the declaration of a class or enumeration» (n4713.pdf, C++17).

Да и в пейпере речь только о конструкторах.
И правда… проглядел, что на классы можно было навешивать уже с C++17. Спасибо, сейчас поправлю статью!
Заголовочные файлы bit, numbers

Круто!!!

Три года назад некий antoshkka выступал с докладом о рефлексии без макросов и кодогенерации в C++14 на CppCon.

snake_case

Еще лучше! Только почему упомянутый [[nodiscard]], да и вообще много чего, не snake_case?
И почему воз compile-time рефлексии и ныне там?

std::format

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

Атомики обзавелись функциями wait и notify. Теперь их можно использовать как более легковесные conditional_variable:

Пора бы уже стандартной библиотеке обзавестись мультиплексированным вводом-выводом, чтобы event-based средствами языка можно было спокойно пользоваться, не боясь, что в один прекрасный момент не будет выбора и придется переписывать все на epoll.
> Пора бы уже стандартной библиотеке обзавестись мультиплексированным вводом-выводом

Над этим во всю идёт работа в Networking TS и Process + File IO предложениях.
Алё, какие заголовочные файлы? В C++20 добавили модули, за ними будущее и настоящее!
Есть механизмы, позволяющие делать модули. Но нет понимания как этими механизмами правильно пользоваться:
* Какого размера должен быть модуль? Сколько сущностей в нём приблизительно должно быть?
* Как правильно разделить стандартную библиотеку, чтобы не получить ABI break и прочие вендоро-специфичные радости?
* Что делать с нативными хедерами? Что делать с модулями, использующими нативные хедера? Реэкспортировать, прятать? Как быть с макросами в нативных хедерах?

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

Поэтому пока заголовочные файлы.
Интересно, а не пофиксили еще, чтобы std::function от movable only объекта тоже была movable. А то захватить unique_ptr в лямбду можно, а что с ней делать дальше не очень понятно.
Когда я в последний раз наступал на эти грабли, стандарт не требовал, чтобы move конструктор std::function использовал move конструктор захваченного функтора. И libc++ при samll object optimization тупо копировал лямбду. Причем без слома бинарной совместимость это, к сожалению, не лечилось.
Интересно, а не пофиксили еще, чтобы std::function от movable only объекта тоже была movable. А то захватить unique_ptr в лямбду можно, а что с ней делать дальше не очень понятно.

Есть проползал на std::any_invokable, как noncopyable замену std::function. Кажется, в с++20 уже не попадет. Впрочем, для таких целей можно использовать std::packaged_task, правда, с некоторой лишней мишурой
any_invokable должен был успеть попасть в C++20, но как и stacktrace — не успел.
А что там с reflection? Есть шанс, что сие чудо бробъеться в стандарт хотя бы C++23?
Рефлексию планируют строить используя constexpr string и constexpr vector. Эти constexpr контейнеры успели проскочить в C++20, а значит шансы увидеть рефлексию в C++23 высоки как никогда.

Не хватает двух вещей: мешка "травы" для комитета и машинки времени для остальных.

Кстати, а есть в планах сделать «финальный» обзор фич по С++20, с примерами, разъяснениями? Понимаю, что это огромный труд и скорее всего нужно разбить на несколько частей, но все же, есть в планах? :)

p.s. для MSVC таблица С++XX фич и доступность по версиям студии, вдруг кому будет интересно. Частично С++20 уже есть в MSVC 2019
docs.microsoft.com/en-us/cpp/overview/visual-cpp-language-conformance?view=vs-2019
Не планировал. Можно ведь пройтись по опубликованным статьям о новинках C++20 от некого antoshkka, и получить полный список новинок с небольшими примерами.

Но это не user friendly подход. Возможно и правда сделаю небольшую статью с самыми главными новинками.
Лучше бы вырвиглазный синтаксис пофиксили, который выглядит чрезмерно перегруженным.
Трудность C++ не в ручном управлении памяти, а именно в синтаксисе и странных конструкциях…
Вот в C# все лаконично и красиво.
Тему Unified Call Syntax не поднимали? Года 3-4 назад Страуструп активно выступал за эту идею, но потом отложили на неопределённый срок. Очень уж этой фичи мне не хватает чтобы красивый код писать =)
Не поднимали. Три года назад в идее были найдены проблемы, и с тех пор не было бумаги с фиксом.
Имхо, пусть закопают и забудут. Мне в плюсах не хватало только на каждый вызов функции гадать а какая конкретно дёрнется.
Тогда давайте и перегрузку функций и методов из языка выбросим, что уж. А то так сложно догадаться, какая именно вызовется =)
Если бы не факт, что некоторые вещи, иначе как через перегрузку, не делаются — я бы поддержал такой пропозал.

К сожалению с современном C++ отказаться от перегрузки можно не всегда (это вам на Pascal, тут конструктор не может произвольного имени иметь), так что от перегрузок отказаться не получится… но усугублять-то зачем?
Тогда давайте и перегрузку функций и методов из языка выбросим, что уж. А то так сложно догадаться, какая именно вызовется

истинно так
std::vector v(5);
std::vector v{5};
std::vector v(5, 5);
std::vector v{5, 5};

сходу сказать какие вектора создадутся в итоге можно только имея немалый опыт. И это для известного типа, если он неизвестен то всё ещё печальнее.
Если еще не добавили — добавте syntax parsing для легкого написания интерпретаторов языков. Еще бы хотелось что-то типа c++script и с++shell для быстрого расширения своих программ всякими скриптами.
Понять бы ещё что вы тут просите. Второе — точно на в комитет по стандартизации, первое — я даже не понял о чём речь…
Например в языке perl6 или haskel есть конструкции, облегчающие парсинг произвольнных языков.

Остальное — стандартные расширения, которые уже есть в java или интерпретируемых языках типа python. Жесткая программа-движок на ходу расширяется скриптами, которые может править продвинутый пользователь.
UFO just landed and posted this here
Например в языке perl6 или haskel есть конструкции, облегчающие парсинг произвольнных языков.
Ну так пишите пропозал. В конце-концов и регулярных выражений бы в C++ не было, если бы кто-то не принёс соотвествующую разработку. Не забудьте только объяснить — зачем это обязательно иметь в стандартной библиотеке и всякие Ragel'и и Bison'ы не годятся.

Остальное — стандартные расширения, которые уже есть в java или интерпретируемых языках типа python. Жесткая программа-движок на ходу расширяется скриптами, которые может править продвинутый пользователь.
Ну и причём тут C++ стандарт? Он не описывает даже как у вас C++ в бинарник превращается, не говоря уже о встраивании компилятора куда-либо.

Это вам к разработчикам компиляторов нужно обращаться в первую очередь — если их это заинтересует, то… всё может быть.
Над вторырым работают в рамках JIT. Но там всё на начальной стадии, всё ещё 10 раз поменяется.

По повожу первого — никто не предлагал. Вы можете стать первым ;)
И всё же, из-за этих co_co_co_рутин мне впервые за 15 лет хочется перейти на другой язык) Даже не используя их, я буду знать, что они там есть)
Почему все так придираются к этому «co_»? Нормально оно выглядит. Мне, например, даже понравилась идея выделить эту группу ключевых слов общим префиксом. Но, судя по комментариям, я один доволен этим выбором =)
Потому, что можно было сделать лучше. Потому, что co_return и return делают примерно одинаковые вещи в разных контекстах и их нельзя путать (а то будет UB). Потому, что с++ похоже будет единственным из всех яп с уродливыми ключевыми словами. Потому, что даже банально читать и печатать избыточные co_ неприятно
А можно вообще всем подосрать, добавив namespace co {}
Можно будет писать и
template <class T>
T function(string s) {
    if (s.empty()) {
        return {}; // OK
    }
    co::await query(s);
    co::yield s;
    co::return {"Done: " + s};
}

и
template <class T>
T function(string s) {
    using namespace co;
    if (s.empty()) {
        return {}; // OK
    }
    await query(s);
    yield s;
    return {"Done: " + s};
}
«async» несколько лаконичнее «using namespace co;» и лишен проблем using namespace. Плюс, co_await/co_yield/co_return это ключевые слова, их в namespace'ы не сунешь
Впервые корутины изобрели оракл. Называлось pipeline функции. Очень помогают при обработке данных. Во многих языках уже присутствует.
Я ничего не имею против самих корутин, даже наоборот. Мне до боли в глазах не нравятся префиксы co_ в зарезервированных словах) Угнетают моё чувство прекрасного, так сказать.
Только вот первая научная статья по корутинам появилась в 60х, лет за 15 до того как Oracle был основан.

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


например .....?

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

Я видел это. Но это скорее стайл гайд: не надо делать, пишете вот так. Он больше не про язык, а скорее про правильные паттерны и про так, как избежать велосипедостроения на пустом месте. Мне больше интересен список с ключевыми словами и технологиями которые больше использовать не рекомендуется. Просто понятия Deprecated в языке то по сути нет, ибо постулируется полная обратная совместимость, в отличии от условного Python 3 или PHP 7.

Да как нет, deprecated auto_ptr например; ключевой слово auto в старом значении тоже выкинули. Выкинули из языка триграфы.
Просто выкидывание фич из библиотеки происходит просто медленно, а из самого языка — еще в десять раз медленнее. Но в C++20 «депрекейчение» вроде пободрее пошло (в STL).
Абсолютная совместимость не постулируется. Стараются лишь чтобы как можно больше кода не сломалось при переходе на новый стандарт. Ну разумеется, недопустимо, чтобы код при сборке с новыми стандартом стал работать по-другому, а в редких случаях, когда сборка ломается — изменения таки имеют шанс попасть в новую версию (опять же, если все согласятся что это очень редкий кейс).

Сырые указатели, да, никто в обозримом будущем не выкинет (как это было в первоапрельской шутке)
Сырые указатели, да, никто в обозримом будущем не выкинет (как это было в первоапрельской шутке)

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

Если вы хотите язык без указателей — зачем вам вообще С++?

Кто сказал, что вообще без указателей? Там выше речь шла не об этом.

Идею я вашу понял, я сам о подобном думал. Беда в том, что в таком режиме станет неюзабельной и STL, и 98% всех существующих библиотек. (посмотрите под капот того же variant — дичь же!).
Возможно ситуацию спасет введение в язык какого-то unsafe блока по аналогии с Rust — в который можно пихать что угодно (ну или #pragma unsafe — но с препроцессором тяжело проконтролировать что оно за пределы файла не уедете). И вот в комбинации с этим блоком, да, можно вводить -safe ключ, который запрещает сырые указатели и что там еще вам не нравится :)
(проблема больше в том, что достаточно сложно определить чем там можно отстрелить себе ногу — например, висячие ссылки тоже никто не отменял, а и без указателей и без ссылок вообще не понятно как эффективный код писать).

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

который запрещает сырые указатели и что там еще вам не нравится

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

UFO just landed and posted this here
Да, спасибо за дополнение, про это знаю, просто не стал коммент раздувать, думал пары контрпримеров достаточно для «в языке нет deprecated».

На какой из? D1 времен 1999-2002 годов отличался от D1 времен 2005-2006 и, тем более от D1 после 2007-го. Ранний D2 так же серьезно отличается от текущего D2. А текущий D2, вероятно, вскоре будет отличаться от D2 2020-го или 2021-го года.

Ранний D2 мало чем отличается от современного — основная парадигма та же. Ведь не зря язык в своё время перезагрузили, оставив в прошлом тот вариант, что сейчас называется D1.
Это мне одному кажется что из парохода делают паровоз?
Отклонили всё, включая наше предложение на отбрасывание co_ с ключевых слов для корутин.
После длительного обсуждения решили, что контракты в принципе не готовы к C++20. И убрали их из стандарта.
Хнык…
Пожалуй, больше всего жаль, что Networking не включили. Но хорошо, что оператор тройного сравнения «космический корабль» добавили, стоящая вещь.
Кстати, STL на модулях может и не сделали, но вы ведь, кажется, разрабатываете boost. Может быть там модули пригодятся раньше 2023 года?
По моему мнению, в boost даже в часть библиотек внедрить модули будет сложнее чем в STL. Хотя бы по тому, что бусту нужно быть совместимым с кучей реализаций, а STL как правило все же под один компилятор затачивается (даже если несколько, то все равно требования к версии жесткие). Ну и объемы кода несравнимые опять же.
Я предполагаю, что возможно какие-то новые и максимально независимые библиотеки, вроде Boost.Stacktrace могут получить поддержку в виде модулей, но не более того.
Я планировал перевести на модули boost::variant, boost::any, boost::stacktrace и boost::lexical_cast. В основном чтобы получить feedback от пользователей, понять как наиболее правильно поддерживать модульные и устаревшие сборки.

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

any и variant ведь в STL переехали — а компиляторов с поддержкой модулей, но без поддержки C++20 вроде как в природе быть не должно…
any простой и подойдёт для разминки. В добавок сейчас готовится any с регулируемым размером SSO, которого в стандарте нет: github.com/boostorg/any/pull/12

variant активно использует макросы и много хитростей с forward declared типами. Его перенос позволит протестировать имплементации модулей в различных компиляторах в режиме хардкор. К тому же он отличается от std::variant, имеет дополнительные возможности и многие кодовые базы не могут достаточно легко переехать с boost::variant на std::variant. Будет полезно тем проектам, которые используют новые компиляторы, но boost::variant.
Ясно, спасибо. Конечно, было бы ещё хорошо перевести asio и process, раз уж мы не увидим сети и процессы в новом стандарте.
А насчёт линейной алгебры что именно предлагается, включить в стандарт BLAS?
Работа кипит. Но вначале нужно протащить в C++ рефлексию. Скорее всего рефлексия будет к C++23, метаклассы скорее всего припозднятся
Дык в чём проблема? Отодвигать пенсию все правительства в последнее время научились. Какие-нибудь немцы или грузины давно это проделали — Россия, как раз, держалась дольше всех. Нужно будет — ещё отодвинут. Долго ли, умеючи.

Напомните, пожалуйста, что означает такая запись


void f(int(&)[]);

Я на С++ не пишу, но с интересом слежу за его развитием.

По контексту — ссылка на массив неизвестного размера. Почти int*, но чуть-чуть больше информации.

Спасибо. А что будет, если присвоить этому параметру новое значение?


void f(int(&)a[]) {

    int tmp[20];
    a = tmp;

}

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

ошибка будет раньше, потому что надо void f(int(&a)[])
а так
ошибка: неправильное использование массива с неопределенными границами
a = tmp;

А так тоже нельзя делать?


void f(int (&a)[], int (&b)[]) {
    a = b;
}