Как стать автором
Обновить

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

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

Никто не написал предложение о их включении в стандарт. Вы можете стать первым :)

Ну логично чтобы это были те же люди которые пишут компиляторы:) Разработчики, мейнтейнеры. Они кстати участвуют в стандартизации C++ ?

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

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

В основном от них исходит критика, когда что-то из предложений нереально реализовать…

Другими словами: они будут критиковать предложение внести в стандарт расширение от конкурентов.

Тоже случается, но очень редко. Как правило разработчики компиляторов оказываются «по одну сторону баррикад»: реализуемость фич коррелирует скорее со свойсвами фич, чем со свойствами компилятора.
Да, участвуют. Все предложения проходят через них, без их олобрения предложение принято не будет
НЛО прилетело и опубликовало эту надпись здесь
Ответы (довольно очевидные, в общем):
1. Описание языка в antlr или, тем более, json, принципиально невозможно, так как парсер языка C++ обязан включать в себя интерпретатор достаточно большого подмножества языка C++ (неожиданно, но факт).
2. Какие 3-символьные сокращения вам вдруг потребовались и зачем?
3. Триграфы, видимо -).
while -> whi ))
Вас ассемблер покусал? ))
НЛО прилетело и опубликовало эту надпись здесь
Вопрос конечно больше к Микрософт. Почему нет такого для С++?
А… зачем? Что в с этим собрались делать и как вам наличие описания, которое не позволяет распарсить код поможет?

Эти спеки из чего-то культурно сгенерированы. И полагаю, что не из XML. Хотя возможно.
Ну фиг его знает в каком оно виде существует. Можете взять исходники, если очень нужно, и распарсить. Ориентируясь на nontermdef, terminal и так далее.

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

Если я ничего не путаю, то вот вам пример:


constexpr bool foo(int n) { ... }

template<bool b> struct bar {
    static int value;
}

template <> struct bar<true> {
    template<int n> value { }
}

template <int n> struct baz : bar<foo(n)> { }

baz<42>::value<3> v;

В зависимости от значения, которое вернёт функция foo, последняя строчка может оказаться как объявлением переменной bar<true>::value<42> v, так и выражением (bar<false>::value < 3) > v

Это вы адатировали пример ещё аж с C++98 для C++11 (там не было constexpr функций, но можно было использовать sizeof).

В C++11 с появлением constexpr фукнций всё стало гораздо веселее. Можно много всякого наустраивать такого, что без интерпретирования кода вы это не распарсите…

Главная фишка: грамматика C++ устроена так, что её нельзя распарсить не умея отвечать на вопрос «это имя — это имя типа, шаблона или чего-то там ещё». Отсюда, кстати, необходимость помечать через typename и template зависимые типы и шаблоны внутри шаблонных функций.
ANTLR грамматика в формате JSON? Это вообще как?

text -> string -> JSON с одной строкой: о)

Нет ли планов на будущее добавить в стандарт контейнер для работы с матрицами, наподобие библиотеки numpy в python? А то уже С++20, а двухмерный массив нужно делать или в C-стиле, или vector<vector>.
Зачем? numpy внешняя либа, пусть и в крестах так будет.
Может не совсем аналог numpy, но контейнера для работы с многомерными массивами не хватает. В boost есть ndarray, но boost не всегда можна использовать.
Сейчас идёт работа над включением в стандарт BLAS и LAPACK. Будут вектора и матрицы, со всеми операциями над ними.

Отлично. Хорошо бы сразу предусмотреть выравнивание строк в памяти. Это в частности полезно для работы с текстурами.

Это немного не то. Может быть требование чтобы размер каждой строки был выровнен например по 4 байта.
Для этого есть alignas. Он ещё дольше существует.

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

Параллелизм тоже сразу будет в раздаче?

Добавляйте DNN (convolution / grouped-convolution) — это не всегда оптимально через GEMM из BLAS реализовывать.
Если вам чего-то не хватает в существующем предложении — пожалуйста, изложите подробно свои мысли и, желательно, напишите proposal. Так же приложите ссылки на предлагаемые для включения прототипы.
Вот бы glm перенесли, если речь зашла о матрицах
glm поддерживает SIMD оптимизации, это хороший аргумент в сторону glm. Мне нравится что linalg в одном хедаре, хотя настраивается это всего один раз)
Почему все так боятся поломать совместимость со старым кодом? Если авторы не хотят мигрировать, то legacy-код все так же может компилироваться старой версией компилятора.
Но он не будет линковаться с новым кодом. То есть вы будете вынуждены пересобрать legacy-код с новым компилятором, или, если пересобрать нет возможности, вы на всегда останетесь со старой версией стандарта.
В C++17 ещё далеко не все и всё разжевали и прочувствовали.
Так что когда-то АБИ придётся перекраивать, пусть это будет фишкой 23х плюсов — до его внедрения на практике ещё далеко. К тому же изменений не так уж и много в 20м — скорее чистка стандарта, возможно подготовка к «глобальным» изменениям в 23м… ИМХО.
Хм, если Concepts, Coroutines, Modules, Ranges для вас небольшие изменения, то что же является большими?
Ranges — просто библиотека, хоть и классная, могла бы появиться ещё в 11. Поправьте, если ошибаюсь.
Concepts — отлично для шаблонного кода, но не позволяет делаеть ничего нового, чего нельзя было бы раньше. Тоже поправьте, если что.
Модули — поверю, когда увижу, что подключение новых 3rdparty библиотек перестало быть болью в голове и заднице :)
Корутины — прикольно, но специфично, запутанная фича для написания запутанного кода с неочевидным control flow. И, помню, в предыдущих TS недоставало реализации каких-то классов, когда компиляторы уже поддерживали корутины. В С++20 всё нужное будет из коробки?
поверю, когда увижу, что подключение новых 3rdparty библиотек перестало быть болью в голове и заднице

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

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

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


1) Нужна сборка для конкретно твоего компилятора и возможно твоего C++ runtime для того чтобы использовать чужой код, поэтому очень часто нужно собирать из исходников
2) В сторонней библиотеке используется система сборки X, а у тебя Y


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


Не уверен как тут модули могут помочь. Адепты "однозаголовочных библиотек" не добавляли даже один .cpp файл (ну кроме тестов), не вижу причин чтобы они перешли на модули. А не для однозаголовочных библиотек все также система сборки X vs Y.

Проблема не в системе сборки как таковой, мне не трудно скопом добавить все исходники из /src/ в свою систему сборки. Источник проблем — запутанная, сложная, непонятная конфигурация с кучей макросов и/или условным включением файлов.
Так что да, мой выбор №1 — header-only библиотека, а №2 — отказ от библиотеки с нетривиальной сборкой.
Header-only библиотеки очень сильно замедляют сборку проекта. И precompiled headers не сильно с этим помогают. В последнее время все стали делать хорошие cmake файлы, которые удобно подключать. Вот подключение библиотеки в 3 строчки, cmake сам выкачает и подключит.
FetchContent_Declare(
  Catch2
  GIT_REPOSITORY https://github.com/catchorg/Catch2.git
  GIT_TAG        v2.13.4
)
FetchContent_MakeAvailable(Catch2)
target_link_libraries(${PROJECT_NAME} Catch2)
Разве это не сделает библиотеки более модульными, а значит, удобными в использовании?

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


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

НЛО прилетело и опубликовало эту надпись здесь
Это личный опыт? А какие сборки тестировались? Инкрементальные, полные, unity билды?
НЛО прилетело и опубликовало эту надпись здесь
Не весь код, а только импортированные символы. Потом для перекомпиляции зависимости можно не полагаться на timestamp source файла, а на хэш бинарного интерфейса модуля, что ещё уменьшит количество перекомпиляций.
Не, тут есть простор для оптимизаций.
Ranges и datetime — добавлили отличные библиотеки для рантайма
Концепты — добавили для метапрограммирования и не только
Модули — ускоряют компиляцию
Корутины — позволяют использлвать нечто абсолютно новое
Constexpr всякое — позволяют compile time вычисления делать с обычным синтаксисом
Новые правила для тривиальных типов — ускорение рантайм кода


Тут каждый аспект языка улучшили. Вы чего-то ещё большего хотели? Скажите хоть чего именно :)
Тут каждый аспект языка улучшили. Вы чего-то ещё большего хотели? Скажите хоть чего именно :)

Например, чтобы когда у клиента падает приложение, а как вы знаете, приложения на С++ при ошибках программирования любят падать, можно было получить хоть какую-то информацию для диагностики. Я о std::stacktrace.

Или, например, сеть. Это было бы действительно принципиальным расширением функциональности языка, как поддержка многопточности в С++11.
Ни в коем случае не хочу преуменьшать нововведения С++20, я очень рад, что мой любимый язык развивается, и последние 10 лет делает это стабильно и по плану, а не как 20 лет до того. Но полностью согласен с автором сообщения, которое мы уже так долго комментируем: С++20 — это больше patch release, чем принципиальные инновации в языке. Ближе всего к инновациям, пожалуй, корутины (к которым я субъективно настроен скептически).
Это было бы действительно принципиальным расширением функциональности языка, как поддержка многопточности в С++11.
Между поддержкой сети есть разница — и она принципиальна: поддержку сети можно добавить сторонней библиотекой, а вот поддержку многопоточности — нет.

Хотя поддержка сети бала бы полезной вещью, спору нет, но мне кажется рановато пока: придётся поддерживать IPv4 и всю связанную с ним муру. Вот в C++23 или C++26 уже можно вносить поддержку, жёстко завязанную на IPv6…

Ближе всего к инновациям, пожалуй, корутины (к которым я субъективно настроен скептически).
Короутины изменят «всё» (в некотором смысле) — так же как метапрограммирование изменило «всё» (в некотором смысле).

Однако произойдёт это скорее ближе к 2030му, чем к 2022му. По одной простой причине: компиляторы пока оптимизируют короутины отвратительно — а раз так, то разработчики будут их избегать. А раз их избегают разработчики — не очень будут напрягаться и компиляторщики…

Тут нужна какая-нибудь популярная библиотека на корутинах (как STL в своё время).
НЛО прилетело и опубликовало эту надпись здесь
Нет — я реалист. Сегодня поддержка IPv6 — примерно у 30%. Порог 50% — будет где-то через 3-4 года. После пересечения границы 50% можно спокойно перестать в новых проектах IPv4.

Эта простая эвристика, неплохо работающая: примерно так было и с Windows XP и со многим другим.

Наличие отдельных «отстающих» групп (Китай у Windows XP, Россия у IPv4) ничего не меняет: если большинство уже перешло и поддержки больше нет — то это их проюлемы, не ваши.

Плюс, опять-таки многое зависит от API: если API будет достаточно высокоуровневым, то факт существования и использования IPv4 может оказаться скрфтым от приложения.
чтобы прилагать какие-то усилия к переходу нужна мотивация. У крупных хостингов/провайдеров и прочих интернет компаний такая мотивация идет от того, что IPv4 адреса начинают потихоньку заканчиваться и, соответственно, дорожать. А вот у всего среднемелкого, коего всё-таки большинство, такой мотивации нет и в обозримом будущем не появится. И я подозреваю что если какой-нибудь гугл начнет популяризацию, например ухудшит ранжирование сайтов без IPv6, их закидают всяким непотребством
Нужно появление 1 (одного) популярного приложения, разработчиков которого задолбает борьба с IPv4. После чего всё случится очень быстро.

У меня где-то дома валяется стаеренькая книжка Novell, которая описывает как IPX и TCP/IP будут сосуществовать «в ближайшие 10 лет».

Вот только вышла она в 1994м… А до 2004го IPX дожил только в каких-то заповедно-исторических нишах… Ибо появление WWW немедленно сделало IPX устаревшим.

То же самое, скорее всего, случится и с IPv4… после пересечения уровня 50%, скорее всего.
придётся поддерживать IPv4

А его в любом случае придется поддерживать. Он никуда деваться не собирается. Не говоря уже о том, что до полноценного пришествия IPv6 мы наверное не доживем.
Доживём, не бойтесь. Процесс перехода идёт, хоть и с некоторым отставанием от плана.
Только это не изменит того факта, что ipv4 никуда не денется. Банально не нужно ipv6 поднимать везде и всюду. Не только интернетом единым все живет.
Ну да, так же и про IPX говорили четверть века назад. Вот один-в-один те же слова.

IPX убил взрывной рост IT мира. На рубеже веков покупка и обновление магистрального и провайдерского оборудования шла такими темпами, что реально раз в 2-3 года провайдеры меняли у себя все, вплоть до кабелей и секретарш (на последнее тоже были деньги). IPV6 вышел в момент когда большинство провайдеров уже все сделали, соcтавили планы на 10 лет и дрожат над капекc/опекс, поскольку прибыли поужались по сравнению с началом века, а затраты растут. Поэтому IPV6 активно появляется у новичков или совместно с какими-то глобальными модернизациями, типа замены кабелей и оконечного оборудования.

IPV6 вышел в момент когда большинство провайдеров уже все сделали, соcтавили планы на 10 лет и дрожат над капекc/опекс, поскольку прибыли поужались по сравнению с началом века, а затраты растут.
Вот только было это почти 10 лет назад. Если посмотреть назад, то 1% был аж в 2013м году.

У нас в локалке, кстати, IPv4 больше нету. NAT64 есть, правда.

Поэтому IPV6 активно появляется у новичков или совместно с какими-то глобальными модернизациями, типа замены кабелей и оконечного оборудования.
Ну да, конечно. И вы хотите сказать, что в Германии поменяли за последние 10 лет половину оборудования, а в Росии — меньше 5%.

Как ни странно, но в Германии как раз таки идет активная модернизация, ибо сети там отстали от тех же США чудовищно. Основная технология там по-прежнему ADSL. В РФ же из-за позднего старта система в целом довольно современная и срок её устаревания ещё не пришел. Кроме того в РФ сейчас заканчивается фаза поглащений мелких провайдеров. Грубо говоря всем был не до модернизаций.

По одной простой причине: компиляторы пока оптимизируют короутины отвратительно — а раз так, то разработчики будут их избегать.
хехе, решение на отвратительно оптимизированных корутинах всё еще заметно быстрее и на порядки выразительнее async/thread и велосипедов на их основе построенных, так что нет, избегать их точно не будут. Я сам их уже заждался — есть легаси, которое приходится поддерживать и оно так и просится быть переписанным на корутины
хехе, решение на отвратительно оптимизированных корутинах всё еще заметно быстрее и на порядки выразительнее async/thread и велосипедов на их основе построенных
Ну threads — да, это понятно. Но короутины напрашиваются на ranges и вообще любые циклы for… и вот там то, что творят современные компиляторы — это форменное непосредство…

На самом деле, самое интересное — это как раз интегрировать корутины с сетевой библиотекой, чтобы псевдоблокирующая семантика в коде транслировалась в co_yield + неблокирующий ввод/вывод. Жаль, что совсем по-человечески это без дополнительной поддержки со стороны ОС не сделать (кстати, в винде с IOCP это должно имплементироваться проще, чем в юниксах), но даже наполовину по-человечески будет уже огромным прорывом.

А что не так с поддержкой IPv4, и о какой "связанной с ним муре" идёт речь?


Если речь идёт о стандартной библиотеке, а не о реализации TCP/IP (которой в стандартной библиотеке делать нечего!), то вся поддержка IPv4 должна заключаться в парсинге адреса и его передаче ОС, ну и в обратную сторону.

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

Если жёстко зафиксировать, что адрес = 128 bit (без вариантов), транспорт — IPv6 (без вариантов) и так далее, то интерфейс заметно упростится.

А внутри либы да — можно спрятать поддержку IPv4 от приложения, тут нет проблем.

А все другие протоколы идут лесом? Даже если вы откажитесь от IPv4 — Fibre Channel и MPLS никуда не денутся. А ведь есть ещё оверлейные сети со своими адресами.


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

А все другие протоколы идут лесом?
Да. Легко.

Даже если вы откажитесь от IPv4 — Fibre Channel и MPLS никуда не денутся.
MPLS вообще на другом уровне работает, на клиентский API он не влияет никак. Fibre Channel — штука интересная, но совершенно непонятно каким боком касающаяся прикладного кода. Если очень нужно — можно его тоже «вложить» в IPv6, благо трёхбайтные FCID в 128битный IPv6 вкладывается тривиально.

Да что там, банальный AF_UNIX живее всех живых, но влезать в 128 бит наотрез отказывается.
И не нужно: много у нас систем с поддержкой AF_UNIX, но без поддержки IPv6 остались?

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

Так что скорее всего будет очередной дико неэффективный монстр в духе … а жаль.
И не нужно: много у нас систем с поддержкой AF_UNIX, но без поддержки IPv6 остались?

А где тут связь-то? Почему для использования AF_UNIX система должна остаться без поддержки IPv6?

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

А если вы разрабатываете что-то новое, то костыль в виде AF_UNIX — вам не особо и нужен.

Но да, это, как бы, не стиль C++… тут скорее в библиотеку запихнут поддержку всего, включая AppleTalk, а потом будут думать как сделать так, чтобы вот это вот всё не сильно тормозило.
Вы наверное что-то путаете. AF_UNIX вам может пригодиться вне зависимости от поддержки IPv6, хотя бы потому, что он производительнее чем использование TCP/IP стека
Ну уж называть AF_UNIX костылем… Имхо, сейчас это очень недооцененная технология. По факту он:
1. Производительнее любого другого стека, так как ни при каких случаях не покидает пределов хоста.
2. Безопаснее, так как к нему невозможно получить доступ извне. Ни прослушать, ни взломать вас через него не смогут.
3. Имеет встроенное разграничение доступа на уровне fs. Мало иметь доступ к системе, чтобы получить доступ к сокету нужно еще и исполнять код от имени пользователя/группы, которым этот доступ разрешен.

Для любого взаимодействия внутри машины AF_UNIX будет предпочтительнее, чем AF_INET6/AF_INET. Это просто протоколы с разными целями.
НЛО прилетело и опубликовало эту надпись здесь
Concepts — отлично для шаблонного кода, но не позволяет делаеть ничего нового, чего нельзя было бы раньше. Тоже поправьте, если что.
да, можно, но среднему с++ программисту сейчас нужна дока чтобы написать простейший ниблоид.
Корутины — прикольно, но специфично, запутанная фича для написания запутанного кода с неочевидным control flow
киллер фича корутин — их control flow прямолинеен, очевиднее уже попросту некуда
Зачем среднему программисту писать ниблоид? Сколько всякой constexpr фигни на шаблонах понаписывал за последние года три — ни разу с такой задачей не столкнулся. Я, конечно же, не обобщаю свой опыт на всех программистов С++, но проблема кажется очень синтетической. Для чего лично мне нужны концепты — замена уродливых enable_if и более осмысленные сообщения об ошибках.

А проблема ли это? Сейчас в линуксе есть libstdc++.so.6, ну будет libstdc++.so.7 для нового C++, а параллельно останется шестая версия для старого софта со старым ABI. В Windows и подавно есть WinSxS для предоставления нужных версий библиотек каждой программе (если не ошибаюсь). Да, для legacy придётся жить со «старым» стандартом, но на то он и legacy. Не самая страшная ситуация, бывает, нужно вообще виртуалку ставить полноценную, потому что из-за слишком нового ядра и слишком старой libc вообще ничего не запускается, сразу с сегфолтом падает.

А проблема ли это?
Да, проблема.

Сейчас в линуксе есть libstdc++.so.6, ну будет libstdc++.so.7 для нового C++, а параллельно останется шестая версия для старого софта со старым ABI.
Когда libstdc++.so.6 появился (а было это в далёком от нас уже сегодня 2004м году) — расколбас был конкретный. А тогда библиотек было ещё написано куда как меньше, чем сегодня.

Переход на libstdc++.so.7, скорее всего, не случится никогда (точно так же как и libc тоже будет, уже навечно, будет libc.so.6), но даже изменения, случившиеся в 2015м (когда появился _GLIBCXX_USE_CXX11_ABI и всё с ним связанное) тоже были довольно-таки тяжёлыми и в результате многие организации перешли на C++11 вот буквально год-два назад.

В Windows и подавно есть WinSxS для предоставления нужных версий библиотек каждой программе (если не ошибаюсь).
Windows — случай особый. Там даже системная C библиотека меняется несовместимым образом.

Да, для legacy придётся жить со «старым» стандартом, но на то он и legacy.
Это вам хорошо рассказывать такие сказки если у вас в проекте только десяток человек и всё в одной организации. А если у вас сотни компонент от десятков вендоров, то переход растягивается на годы.
Когда libstdc++.so.6 появился (а было это в далёком от нас уже сегодня 2004м году) — расколбас был конкретный. А тогда библиотек было ещё написано куда как меньше, чем сегодня.

Так это не повод вообще не делать изменений. Так можно и про systemd рассказать, тоже был расколбас, но ничего, перетащили помаленьку. Речь ведь не о полной поломке совместимости уровня «вчера всё работало, сегодня ничего не работает», а о постепенной миграции. Никто не заставляет компилировать библиотеки самым свежим компилятором, многие до сих пор на до-одиннадцатом C++ живут по разным причинам.


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

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


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

Так можно и про systemd рассказать, тоже был расколбас, но ничего, перетащили помаленьку.
systemd тут причём? Он полностью поддерживал почти все фишки SysvInit поддерживал во-первых, а во вторых — переписать сто тысяч строк скриптов несколько проще, чем сто миллионов строк кода на C++. Как показывает математика — примерно так на три порядка.

Речь ведь не о полной поломке совместимости уровня «вчера всё работало, сегодня ничего не работает», а о постепенной миграции.
Речь идёт именно о такой поломке. Послепенная миграция идёт всё время: в C++17 убрали auto_ptr, в C++20 — raw_storage_iterator и так далее.

Никто не заставляет компилировать библиотеки самым свежим компилятором, многие до сих пор на до-одиннадцатом C++ живут по разным причинам.
Очень мало кто живёт, на самом деле, уже. Именно потому что библиотеки собранные с _GLIBCXX_USE_CXX11_ABI=0 и _GLIBCXX_USE_CXX11_ABI=1 несовместимы.

Теоретически можно в одной библиотеки иметь и _GLIBCXX_USE_CXX11_ABI=0 и _GLIBCXX_USE_CXX11_ABI=1 (в самом libstdc++ так и сделано), но это оказывается настолько неудобно, что этим мало кто заморачивается.

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

Я не знаю, можно ли будет новую и старую загружать одновременно (скорее всего, нет), и тогда да, библиотеки, собранные под разные версии стандарта вместе не уживутся, и программа, нуждающаяся в обеих, не заработает.
Вот в переходе 2004го года этого было делать нельзя — и это был полный кошмар. А уже в 2015 всё достаточно спокойно случилось — но заняло порядке 7-8 лет.

Если в каком-нибудь C++33 будет мало других фишек — можно поломать обратную совместимость и устроить этот аттракцион ещё раз. Но делать так, чтобы С++23 можно было начать пользоватьс только ближе к 2030му году — комитет не захотел.
Там проблема глубже. На libstdc++.so.6 + libstdc++.so.7 проблемы только начинаются: вам нужно собрать отдельный Boost для libstdc++.so.6 и отдельный для libstdc++.so.7… фактически каждая популярная C++ библиотека удвоится.

Поэтому обычно в рамках дистрибутива сразу переходят на новый ABI.

Заметье, что в комитете предлагали сломать ABI на уровне ядра языка. В этом случае у вашего проекта возникают проблемы с поддержкой старых платформ — в C++ вы могли пользоваться новыми языковыми фишками и [очень]старой стандартной библиотекой, но при сломанном ABI вы так больше делать не можете. Новый компилятор будет просто выдавать бинарники не линкуемые со старым стандартом. Поэтому вы будете вынуждены разрабатывать под версию стандарта поддерживаемую всеми вашими платформами. А это замедлит внедрение новых стандартов C++, что не очень хорошая идея.

Ломать ABI можно, но это должно быть осознанное решение вендора для конкретой реализации библиотеки, а не решение комитета на уровне языка. Форсирование слома ABI может быть не поддержано вендором, и мы получим стандарт C++ которым не будут пользоваться на какой-то платформе. Это очень нездоровая ситуация.

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

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

мост между ABI

extern «C»?
На самом деле здесь есть некое лукавство, ибо собственно ABI частью стандарта не является, более того, ABI очень сильно платформно-зависим (даже повсеместно применяемый Itanium C++ ABI содержит очень много архитектурно-зависимых нюансов). В моем понимании комитет может только выдать рекомендации по новому ABI, но никак не стандартизовать его. И решение по переходу на новый ABI в конечном итоге останется за вендором. Но для начала нужно реализовать поддержку нового ABI компиляторами.

На самом деле, я не вижу больших проблем с переходом на новый ABI. Ну будут в переходный период разные версии библиотек с разным ABI, и что в этом такого? Это necessary evil. Вон в винде почти каждое приложение тащит с собой свою версию VC рантайма, и никто не возмущается.

Ну это уже тогда по факту будут два разных языка типа C и С++. Ведь будут обнаруживаться глюки в старых версиях компилятора, их будут править. Потом вводить часть фишек из нового стандарта и других языков и вскоре разделение уже довольно заметным.
Думаю, со временем ранние версии языка отомрут, а код будет переписан.
Вот только время это может измеряться десятилетиями…
Кто может объяснить о каких конкретно проблемах идет речь в этом примере
X *make_x() {
  X *p = (X*)malloc(sizeof(struct X));
  p->a = 1;
  p->b = 2;
  return p;
}
До C++20 стурктура по адресу p не создавалась. Компилятор мог делать странные оптимизации, подразумевая что по p нет объекта
А есть ли случаи такого поведения в дикой природе, или этот UB был полностью «бумажным»?
Большинство известных мне компиляторов так не чудили

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

Upd: возможного чудения, конечно.

Фрагментация жёсткого диска

Мне не пример UB вообще. Мне вот конкретно про данный случай. Что вызовет проблемы?

Upd: хотя фрагментация, конечно, это не форматирование, которое в качестве страшилки про UB детям рассказывается :-)
НЛО прилетело и опубликовало эту надпись здесь
Насколько я понимаю, обращения с р как со структурой — чтение и запись полей — невалидны, потому что эта структура никогда не была сконструирована (не вызвался конструктор, the lifetime of an object has not started). Поэтому такие обращения невалидны, и если компилятор может быть уверен, что конструирования не было, он вправе выкинуть эти обращения вообще, или предположить, что значения полей имеют константное значение, удобное для оптимизации, или предположить, что этот код вообще никогда не может быть фактически вызван, или сгенерировать UD2… Вариантов много, и они вызывают разное непонятное и непредсказуемое поведение.
Пример определенно странный. Может в примере не new потому что хотят выделить одним блоком место под несколько структур за раз. Почему то же вручную задают размер… Или можно предположить, что косяк происходит с алигнментом и некорректным расчетом длинны структуры в каких то редких компиляторах, в результате чего хвост используемой в последствии структуры вылезет за пределы выделенной памяти из-за паддинга элементов структуры. Както так… Я на Шарпе просто уже года три, уже забыл про эти проблемы, могу ошибаться.
З.Ы. Мне кажется им надо не из парохода делать паровоз, а просто создать новый с+++ с новым ABI и его мучить. Кому надо-тот легко переведет программу.
Мне кажется им надо не из парохода делать паровоз,


А чем тогда будут заниматься люди с фотографии?
З.Ы. Мне кажется им надо не из парохода делать паровоз, а просто создать новый с+++ с новым ABI и его мучить. Кому надо-тот легко переведет программу.
А раскажите-ка, как в вашей альтернативной вселенной, где Pascal давно умер, а Modula-2 и её потомки процветают, живётся. Интересно же.

А то в найшей вселенной скучно: Delphi как продавался, так и продаётся, Modula-2 и Operon'ы всякие «в дикой природе» не встречаются.

P.S. Успешными в нашей вселенной были только несколько переходов: Visual Basic, Python… этап «лёгкого перевода программ» занимал миниму лет 10 (обычно ближе к 20) и приводил к успеху только тогда, когда главный вендор языка категорически отказывался поддерживать старую версию. Многие языки в результате просто «вылетели на обочину» и ими перестали пользоваться…
Delphi продается далеко не так, как продавался. А Хейлсберг стоял за C#, который продается гораздо лучше.
Delphi продается далеко не так, как продавался.
Однако же Modula-2 и Oberon продаются гораздо, гораздо хуже.

А Хейлсберг стоял за C#, который продается гораздо лучше.
Однако не имеет никакого отношения к Pascal вообще. Речь же идёт не о судьбе комитета по стандартизации C++, а о судьбе C++, как бы…
Речь в комментарии, на который я отвечал, шла про
просто создать новый с+++
.

С тремя плюсами. Будет ли Си с тремя плюсами иметь к C++ большее отношение, чем C# к Pascal — дело личной фантазии каждого.
Будет ли Си с тремя плюсами иметь к C++ большее отношение, чем C# к Pascal — дело личной фантазии каждого.
Вопрос не в отношении C+++ к C++. А в том, что далеко не всем удаётся найти идиотов, готовых «слить» свою компанию ради того, чтобы продвинуть новый модный язык.

Рассчитывать же на то, что таких идиотов найдётся много — уже и совсем бессмысленно.
а я и не говорю что всё так просто, комментами не описать всю прелесть процесса ))). Речь идет о легком переходе в виде лишь перекомпиляции библиотек и программ (если они после этого заработают), но ведь надо еще исключить коллизию со старым кодом…
Это уже получается другой язык. Тогда уж введите в него нормальное понятие string, введите понятие безразмерного char юникодовского, введите в него смарт указатели и на их базе сделайте встроенный сборщик мусора. Создайте четкие стантарты ABI для кроссплатформенности и у вас получится лучше чем Шарп ;)
Создайте четкие стантарты ABI для кроссплатформенности и у вас получится лучше чем Шарп ;)
Совершенно не факт.

Уж сколько уж было подобных попыток. Начиная с PL/I — тот тоже дожен был заменить и Fortran и Cobol… результат — пшик.
Пример: Структура не создана, значит и запись в неё не имеет смысла и можно её не производить.

Другой пример: структура не создана, а значит и последующие чтения из неё не имеют смысла и можно заменить на чтение 0.
У нее же нету пресловутой non-vacuous initialization. Выделили storage — началось lifetime. Нет?

По стандарту разрешено обращаться к участку памяти только* через указатель тип которого совпадает с "effective type" этого участка памяти. Так вот у узастка памяти выделенного malloc такого типа нет. Поэтому тот факт, что мы интерпретируем его как X нарушает strict aliasing. С этого момента даже самое бредовое поведение будет находится в рамках стандарта. Например компилятор может посчитать что код после malloc не имеет наблюдаемых эффектов за пределами функции, и выкинуть его.


    • на самом деле не только. Гуглите strict aliasing

Ничего не понял

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

О чем вообще идет речь? Что malloc будет создавать еще и таблицу виртуальных функций, а memset 0 будет её обходить стороной и работать как new без вызова конструктора? Или что?
Ну, видимо, да. Про конструкторы-деструкторы-виртуальные функции и из них вытекающее.
Ни в коем случае. Все типы, которые могут таким способом возникать должны иметь конструирущий код из нуля байт и деструктор из нуля байт.

Если тип нетривиален, то его так создавать нельзя.
Создавая объект, компилятор создает только упорядоченный блок памяти, в котором есть блок памяти, который вы интерпретируете либо как данные структуры, либо интерпретируете как указатели на методы, среди которых для классов/структур есть такое понятие как конструктор и деструктор, которые вызываются при new/delete. При malloc ничего не вызывается, а только выделяется память.
И вот в этом примере и вызывать нечего.
если честно, в данной статье написан бред, хорошо бы глянуть ссылку в стандарте, что имеется в виду, я не нашел ничего про это. То что модели памяти доработали, то это вообще к этому не имеет отношения.
Ну, бред — это сурово сказано. Но пример со структурой совершенно неудачный. Забывают люди корни, увлекаясь модными фишками :-)
Чем неудачный? Этот код нарушает правила языка С++, это не код валидной программы на С++. Пример очень жизненный и правильный, мне всё время приходится держать это в уме и иногда писать более сложный и чуть менее оптимальный код, чтоб не нарушать это правило.
нарушает правила языка С++


Какие? Конструктор тривиальный, выделили storage — lifetime пошло.
Я не к тому, что так надо писать.
выделили storage — lifetime пошло.
Это с какого перепугу-то?

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

Я не к тому, что так надо писать.
Проблема в том, что так, собственно, и пишут — с 70х годов… а стандарт этого не позволяет.

Собственно это просто чёткое описание для разработчиков компилоров того, что в них и так есть (все известные мне компиляторы считает, что malloc создаёт объекты… хотя по стандарту C++17 это не так).

Так что практически, для malloc'а это все не нужно… Нужно для всяких arena-аллокаторов, когда берут кусок памяти, где «жил» объект одного типа, а потом суют туда объект другого типа.

Вот тут может быть… неприятность…
Это с какого перепугу-то?


An object is said to have non-vacuous initialization if it is of a class or aggregate type and it or one of its subobjects is initialized by a constructor other than a trivial default constructor. [ Note: Initialization by a trivial copy/move constructor is non-vacuous initialization. — end note ]
The lifetime of an object of type T begins when:
(1.1) — storage with the proper alignment and size for type T is obtained, and
(1.2) — if the object has non-vacuous initialization, its initialization is complete

Это из драфта 2017, под рукой прямо сейчас ничего другого нет. Но не думаю, что радикально как-то в не-драфте поменялось.
The lifetime of an object of type T begins when:
(1.1) — storage with the proper alignment and size for type T is obtained, and
(1.2) — if the object has non-vacuous initialization, its initialization is complete
лайфтайм начинается когда для объекта выделена память и завершилась его инициализация (если таковая этому классу требуется).
Совершенно верно. Структуре из примера требуется?

Upd: хотя, вообще-то, не совсем верно. Не дословный перевод :-) Но там же в тексте расшифровано, что такое non-vacuous initialization.

Upd2: вот в 20-м драфте уже «its initialization (if any) is complete (including vacuous initialization) „
Совершенно верно. Структуре из примера требуется?
хе-хе, для начала пример не гарантирует выровненность блока памяти под структуру (в зависимости от определения структуры конечно же).

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


Regular malloc aligns memory suitable for any object type (which, in practice, means that it is aligned to alignof(max_align_t)), если повар нам не врет.

Ни одна из этих операций не начинает лайфтайм объекта.


Что же его тогда начинает, если «непраздной» инициализации не требуется, и память нужного размера с нужным выравниванием уже есть?

Regular malloc aligns memory suitable for any object type (which, in practice, means that it is aligned to alignof(max_align_t))
предполагая что структура не overaligned
Что же его тогда начинает, если «непраздной» инициализации не требуется, и память нужного размера с нужным выравниванием уже есть?
давайте на примере:
void* p = aligned_alloc(alignof(T), sizeof(T));
// Здесь мы просто память выделили. Еще никто не сказал что в ней будет лежать T
T* x = (T*)p;
// А здесь мы просто проинтерпретировали какую-то память как объект, мы его наверно где-то раньше создали?
T* y = (T*)p;
// Хм. Мы же точно не создали объект дважды, верно?
По сути, формально буква стандарта и компиляторы чуть-чуть по разному этот кейс трактовали. Вот и подправили стандарт чтобы не было расхождения «формально»/«на деле».
а стандарт этого не позволяет.

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

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

О чем вообще идет речь? Что malloc будет создавать еще и таблицу виртуальных функций, а memset 0 будет её обходить стороной и работать как new без вызова конструктора? Или что?
Речь о том, что поскольку описанная функция всегда вызывает неопределённое поведение, то достаточно грамотный компилятор может, например, выкинуть её из программы вообще.

А теперь программы, которые «кусок памяти» интерпретируют как объект в некоторых, строго оговоренных случаях, будут валидны и проблем с ними не будет.

Всё это напоминает историю с вот таким вот прелестным кодом:
int int_size() {
    int size = 1, count = 1;
    while (count > 0) {
        size++;
        count += count;
    }
    return size;
}
Так вот от момента, когда C89 разрешил компиляторами генерировать вот это:
GCC, для примера
int_size():
.L2:
    jmp     .L2
до момента, когда люди типа вас подняли вселенский вой — прошло больше 10 лет.

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


Слово storage пропало из стандарта?
Нет конечно. Где-то же объекты должны существовать. Но вы нигде и никак не можете оперировать с «сырой памятью» — только с объектами.
raw_storage_iterator и т.п.
и dynamic_cast к void*.
raw_storage_iterator удалён из C++20 — именно потому что никакого способа использовать его в варидной программе на C++ стандарт не давал.
Текущий вариант мог создавать проблемы. А мог и не создавать. Это не то же самое, что принципиальная невозможность использовать, и вообще работать с raw storage. Проблема, кстати, еще с 11-м стандартом возникла. Что не помешало raw_storage_iterator дожить до 20-го.
А мог и не создавать.
Не мог. Практически любая программа с его оспользованием вызывала UB и, соотвественно, программой на C++ не являлась.

Что не помешало raw_storage_iterator дожить до 20-го.
Если бы им реально пользовались, то его и в C++20 не выкинули бы.
Речь о том, что поскольку описанная функция всегда вызывает неопределённое поведение, то достаточно грамотный компилятор может, например, выкинуть её из программы вообще.


Молча, видимо :-)

Именно что молча, как и в случае с большинством UB. Потому-то их так и боятся.

Пример приведите. Как компилятор молча выкидывает из программы функцию, потому что считает, что там UB.

Конкретно этого примера нет, потому что спохватились вовремя.


Вот другой пример, где происходит именно выкидывание (правда, не функции, а цикла): Неопределённое поведение и теорема Ферма


А вот куда более страшный пример: Как может вызваться никогда не вызываемая функция?

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

Так что максимум что может сделать (и делает иногда) компилятор — так это превратить вашу функцию в «пустышку» (пример я приводил).

Но я тут не совсем уверен, что hobogene именно это имеет в виду.
Ооо, какая шикарная подборка настоящих живых УБ, а не обычное «винт тебе форматнёт. А потом брата убьёт.». Спасибо!
Пример вам уже приводили. Или для вас так принципиально, чтобы функция выкинулась вся целиком, вместо того, чтобы там была заглушка, на которой исполнение остановится?
Или для вас так принципиально, чтобы функция выкинулась вся целиком


Вы сами про это написали. Более, того, писали, что это будет именно из-за UB. Я попросил подтвердить примером. Если не вышло, ну и не вышло. Никто не умрет.
НЛО прилетело и опубликовало эту надпись здесь
К сожалению (или к счастью) — нет.

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

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

Так что максимум, что компилятор может сделать — вставить на место функции пустышку. Что я обычно и имею в виду, когда говорю новичкам про то, что «всю вашу функцию компилятор может просто удалить».

И таких примеров — вагон.

Но при этом почему-то именно за эту фразу hobogene зацепился и теперь носится с ней, как с писаной торбой…
НЛО прилетело и опубликовало эту надпись здесь
Но это, конечно, все неважно. ИМХО «функция выкинулась вся» и «от функции остался один лейбл» примерно эквивалентны.
Там не лейбл. Там целый jmp остался.
НЛО прилетело и опубликовало эту надпись здесь
Ну вот не любит gcc совсем пустые функции почему-то…

Нет, виртуальные функции тут ни при чём. Проблема тут в модели памяти С++.


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


Упрощая, как раз памяти не существует, мы работает с объектами.

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

И вот когда с этим попробовали работать — как раз и выяснилось, что куча программ и библиотек к этому нифига не готовы… и, в общем, C++ стандарт отчасти тоже виноват: если на платформе есть intptr_t, то, в соотвествии со стандартом, можно указатель в него преобразовать и обратно тоже. Правда этот тип не является обязательным, так что теоретически стандарт к работе в системе, где всё на свете объекты таки готов.
НЛО прилетело и опубликовало эту надпись здесь
Вы не можете складывать полученные числа, ожидая, что что-то разумное получится (кроме адресации внутри массивов, возможно, но я не уверен).
Зато можете, скажем, записать в файл на диск, а потом прочитать из него.

Ну или организовать XOR-связный список.

При всех этих манипуляциях у вас теряется информация о том, что в данном участке памяти хранится указатель на объект — а это чревато с точки зрения безопасности.

Можно, наверное, предоставить процедуру, которая проверит что имеющееся у вас число, которое не помечено как указатель, на самом деле, указывает на валидный объект, к которому вы имеете право доступа и превратит число в указатель… но представьте себе с какой скоростью при таком подходе будет работать тот же XOR-связный список!
Объект занимает, тем не менее, связную область storage ненулевого размера. Так что указатели в этом примере, наверное, вообще не особо при чем.
Хм… А чем формально вышеописанный код отличается от

file1.cpp
X* malloc_x() {
    return (X*)malloc(sizeof(struct X));
}


file2.cpp:
extern X* malloc_x();

X *make_x() {
  X *p = malloc_x();
  p->a = 1;
  p->b = 2;
  return p;
}


В измененном примере мы вызываем функцию, которая возвращает указатель на объект типа X, и компилятор понятия не имеет, каким образом он получен. Разница только в том, что в исходном примере компилятор видит слово malloc и думет: «ага, мы тут память неинициализированную выделяем», хотя никто ему не давал права судить, что именно malloc() делает семантически.

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

Это сомнительное утверждение. На этапе компиляции нет никаких гарантий, что используется именно стандартная библиотека, поэтому malloc как функция, задекларированная средствами языка, не несет никакой информации о семантике. То, что компилятор делает некоторые (пусть даже верные в 99 процентах случаев) предположения о семантике с целью последующей оптимизации, — это допустимо, но должно быть четко отражено в документации на компилятор, а по-хорошему должно управляться опциями (кстати, интересно, будет ли, скажем, gcc это оптимизировать с -nostdlib?), и должна быть возможность получать предупреждения о подобных оптимизациях.
Это пример сильно отличается от приведенного примера с int_size(), где причиной оптимизации является неопределенное поведение, четко описанное в семантике языка

Ну вот в glibc функция malloc объявлена с атрибутом __attribute__ ((__malloc__)).


Как вы думаете, этого достаточно чтобы компилятор понял, что malloc — это именно malloc, а не что-то ещё?

В данном случае сложно сказать, что является триггером, так как конкретный вышприведенный пример мне не удалось скомпилировать так, чтобы компилятор выкинул инициализацию полей. Т.е. я пробовал и gcc, и clang разных версий — не получается. Кстати, пример можно легко модифицировать с использованием realloc(), у которого атрибута нет (атрибут указывает на невозможность алиасинга, что для realloc'a неверно).
Но то, что стандарт поправили — это хорошо.
так как конкретный вышприведенный пример мне не удалось скомпилировать так, чтобы компилятор выкинул инициализацию полей

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

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

Правильно ли я понимаю, что данная правка обязывает интерпретировать этот блок памяти как структуру при приведении void* к struct * и -> считать обращением к элементам структуры, что и делали ранее все компиляторы, просто сейчас это прописали явно и ввели запрет интерпретировать как-то иначе, хотя я ума не приложу как можно интерпретировать иначе?
Да, всё верно. Это теперь принято называть implicit construction, и оно срабатывает если у типа есть тривиальный конструктор и деструктор

Подскажите, а обсуждалась ли в контексте std::hash возможность отделить описание в объекте что хешировать от конкретного хешера?

Не очень понял вопрос. Приведите пожалуйста пример

Я имею ввиду разделение на Hasher и Hashable, что-то вроде такого


// Хешер, считает хеш
template<typename T>
concept Hasher = requires(T a) {
    { T::hash_type; }
    { a.hash() } -> std::convertible_to<T::hash_type>;
    { a.append(std::span<std::byte>{}) };
};
// Тип, который можно хешировать
template<typename H, typename T>
concept Hashable = Hasher<H> && requires(H h, T t) {
    { append_hash(h, t) };
    // ... или ...
    { t.hash(h); }
};

// ... где-то в коде
struct Foo {
    int first;
    int second;
};

template<Hasher H>
void append_hash(H& hasher, Foo const& foo) {
    append_hash(hasher, foo.first);
    append_hash(hasher, foo.second);
}

int main() {
    Foo foo { 21, 42 };
    std::hasher hasher;
    append_hash(hasher, foo);
    std::cout << "The hash of Foo { 21, 42 } is " << hasher.hash() << "\n";
    return 0;
}

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

Идея в том, чтобы иметь какие-то объекты Hasher, которому уже что-то передавать в тот же operator(), чтобы получить какой-нибудь хеш. Я такую точно от кого-то видел (если не ошибаюсь, то от Jossuttis (не знаю, как его фамилия правильно пишется)), но не могу найти бумаги.
В самом разгаре. Пока всё неочевидно и кажется что 10 раз поменяется
мало того что изменение ломает код как минимум двух компаний
всегда найдется компания, у которой что-то сломается, и которые против изменений ради status quo
Обработчики контракта могут кидать исключение, если исключение кинуть из noexcept функции — будет std::terminate и приложение рухнет
если только проверки предусловий контракта не вынести перед вызовом функции. Тогда и логика будет «noexcept функция не кидает если контракт не нарушен», что логично и просто, и оптимизировать проверки контрактов компилятору будет проще
> всегда найдется компания, у которой что-то сломается, и которые против изменений ради status quo

И это хорошо до тех пор, пока не ломается код вашей компании, или код любимого вами приложения.

> Тогда и логика будет «noexcept функция не кидает если контракт не нарушен», что логично и просто, и оптимизировать проверки контрактов компилятору будет проще

Вас не смущает, что с таким подходом std::is_nothrow_* трейты вам будут врать и вы не сможете написать эффективные библиотеки? И что производительность вектора может ухудшится раз в 10?
И это хорошо до тех пор, пока не ломается код вашей компании, или код любимого вами приложения.

мои любимые приложения и так постоянно ломаются (
И что производительность вектора может ухудшится раз в 10?
так наоборот же. Сейчас is_nothrow_* трейты для методов типа std::vector::back() врут, возвращая false, хотя на самом деле (в большинстве реализаций стандартной библиотеки) там никогда не возникнет исключение. А noexcept специализации всегда (когда они есть) реализуют более эффективный алгоритм.

Ну и хочется иметь noexcept для варианта релизной сборки без рантайм проверок контрактов
std::vector благодаря трейту is_nothrow_move_constructible решает, копировать ему элементы при resize, или же просто перемещать.

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

Кстати, добавьте noexcept к vector::back прям сейчас… и вы не увидите разницы в кодгене для GCC (остальные вендоры ленятся с правильной реализацией исключений)
Если трейт начнёт врать, то вектор возможно что всегда будет копировать. А это безумные замедления, никакие мнимые преимущества от noexcept их не перевесят.
трейт начнет врать если мы начнем навешивать контракты на мув конструкторы/операторы присваивания. А это скорее странный/нежелательный юзкейс нежели типовой
Кстати, добавьте noexcept к vector::back прям сейчас…
vector::back сравнительно хорошо инлайнится, а для более сложных функций разница будет заметной.
остальные вендоры ленятся с правильной реализацией исключений
да с исключениями вообще всё очень плохо. И поэтому noexcept так важен
кстати, можно же еще вопрос иначе поставить: может быть разрешить стандартным библиотекам усиливать noexcept'ness гарантии для не кидающих функций? Т.е. пометить набор функций в стандарте как noexcept(/*implementation-defined*/)?
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
И тут разработчики MS VC «А я говорил, говорил!» :D
НЛО прилетело и опубликовало эту надпись здесь
Используйте это на свой страх и риск. Как будут выглядеть стандартные модули ещё не решено, только зарезервированы имена начинающиеся с std. Если к C++23 комитет решит сделать несколько другие имена модулей, код с вышеозвученными MS модулями немного поломается.
А такие конструкции
import <iostream>;
как относятся к стандарту?
Импортирование C++ хедеров обязано работать в C++20. import <iostream>; — это правильно, безопасно.

Но не так быстро как import std.iostream;, который появится только в C++23
Если к C++23 комитет решит сделать несколько другие имена модулей, код с вышеозвученными MS модулями немного поломается.
надеюсь если захотят поменять реализацию аргумент MS а-ля «а мы уже вендорлокнули» проигнорируют?
Ну если вариант MS будет более менее, а остальные вариантов лучше не предложат, то могут принять MS вариант, ничего не мешает.
Увидим. Хороший будет тест на Microsoft. Исторически у них было… по разному.

Например вот такая вот программка:
#include <stdio.h>
int arr[42];
int main( void ) {
     printf( "%u\n", sizeof( &arr ) );
     return( 0 );
}
Стала вести себя «по правилам» аж в Visual Studio 2008. Visual Studio 2005 всё ещё ведёт по заветам K&R, а не стандарта C89.

Так что не удивлюсь если эти модули будут лет 10 приводиться к стандарту…

А как ввела себя эта программка в VS 2005?

168 печатала
Ну если вариант MS будет более менее, а остальные вариантов лучше не предложат, то могут принять MS вариант, ничего не мешает.
co_co_co_routines заехали такими потому что слишком поздно спохватились менять на нормальный синтаксис; предложение по модулям от МС не вносило буквально ничего в С++ — собственное расширение (которое послужило основой реализации) они использую преимущественно для биндингов к шарпу, виндовому API и прочим COM/OLE. Для них выгодно чтобы их решения становились стандартными даже в ущерб всей индустрии поэтому к предложениям от MS лучше относиться осторожно

Ну здрасьте! Там в Coroutines TS именно что полностью переделали предложение от MS, в том числе заменили синтаксис.


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

Антон, с большим уважением отношусь к твоему вкладу в наше светлое будущее.


Мои впечатления от поста: информация полезная, но написано сложно. Пара примеров:
1) Пример со структурой с C. Насколько я понял, то мораль: "не делай так, снег в башка попадёт". Не совсем понятно, что изменилось: не было специфицировано/ теперь специфицировано, могут ли теперь компиляторы делать "странные оптимизации"? При использовании gcc/clang я не смог добиться от make_x() чего-то некорректного.
2) Иногда порядок слов сложный. Следующая фраза, как пример: "Вы не сможете воспользоваться в проекте с C++23 библиотеками, собранными более ранними версиями C++"


И пара вопросов:
1) Как проходит голосование, например, за такую фундаментальную вещь, как смена ABI:
простым перевесом голосов?
2) Что планируется в направлении Reflection?

1) Согласно букве стандарта раньше было нельзя так делать, но все делали. С C++20 теперь так делать можно.
2) Постарался упростить. Если найдёте ещё сложные предложения — пишите в личку, поправлю.

Ответы на вопросы второй части:
1) да, фактически прочтым перевесом голосов
2) Я очень надеюсь на Reflection в C++23, шансы увидеть его в ближайшем будущем высоки

Разве понятие "implicitly creating objects" вошло в C++20? p0593 ведь отложили до C++23.

Вошло в C++20. Успели в последний момент :)
С включением модулей в C++ 20 можно ожидать какое-то стандартное решение менеджера пакетов для плюсов?
т.е. что-то наподобие maven для jvm или cargo для rust'а

Над пакетными менеджерами работают, но они никогда не зависели от успеха модулей. Так что даже без модулей, работа над стандартными пакетными менеджерами продолжалась бы.
Пока что можете использовать Conan или на крайняк Vcpkg. Они точно не являются стандартными с точки зрения наличия в Стандарте, но сейчас де-факто стандарт, как CMake. Хорошо это или плохо — решать вам.

"Вот, например, такой код абсолютно валиден для C:..."


Нет, он не валиден для C. Язык С в вашем примере не будет знать, что такое X.

Угу. Для C нужно писать:
typedef struct X { int a, b; } X;

Но так уже почти никто не делает…
А если поправить, чтобы был валидным, проблем все равно что-то не видно особых. Вот если плюсовую структуру, которая суть класс, с блэкджеком и каким-нибудь нетривиальным виртуальным деструктором…
С небольшим перевесом голосов решили ABI в C++23 не ломать.

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

Я вот всегда считал очевидным и самим собой разумеющимся, что Стандарт описывает интерфейс, а не имплементацию, а потому никакого ABI в C++ нет, не было и не будет.

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

Банальности уровня «мойте руки перед едой».

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

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

Казалось бы — а причем здесь вообще стандарт, если сохранение бинарного представления — это забота компилятора?

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

Печаль.
Если вдруг надо вынести код в библиотеку — используем C-интерфейсы между модулями
мы тут вообще-то про с++. Да и специфичные для плюсов особенности ABI в си не завернешь
мы тут вообще-то про с++
Да вы что, я и не заметил.
Ок, «интерфейсы с Trivial & Standard Layout типами данных». Так понятнее?

Завернуть можно всё, вопрос в желании.
Ок, «интерфейсы с Trivial & Standard Layout типами данных». Так понятнее?
а еще без перегрузок, исключений, шаблонов, ссылок и (для одинакового манглинга между компиляторами) с extern «C». То есть на си а не плюсах.
Завернуть можно всё, вопрос в желании.
откуда ж возьмется желание пихать сишную прокладку на каждом стыке двух бинарей?
Банальности уровня «мойте руки перед едой».
Вот нифига не банальности.

Выставлять наружу C++ объекты тоже, в принципе, можно, но будет работать только с той же версией того же компилятора и с теми же параметрами сборки, поэтому имеет смысл только в том случае, когда контролируем всю кодовую базу и можем всё пересобрать при необходимости, т.е., в основном, «только для внутреннего потребления».
Вы сейчас описали ситауцию в Windows. А вот в мире Linux и Unix — всё было совсем по другому.

Хотя разные версии стандартной библиотеки и существуют, но этих версий относительно немного: в версии gcc 3.4 в 2004м году (это, на минуточку, 16 лет назад) и в gcc 5.1 в 2015м она была поломана. И во втором случае — это было сделанно именно из-за изменений в стандарте C++, что, как несложно заметить, задержало внедрение C++11 (и так-то потребовавшего нескольких дополнительных лет разработки) ещё на 4 года (при этом ещё и переход достаточно мягкий был: вы можете использовать в GCC 5+ два вида ABI — «старый» из GCC 3.4 и новый из GCC 5.0… в одной программе). Потом ещё пару лет GCC 5+ использовался в режиме совместимости.

И вот именно повторения этой ситуации (затягивание внедрения новой версии C++ лет так на 5-7) комитет и решил избежать…

А теперь, оказывается, в дикой природе нашлись уникумы, решившие, что протаскивать всё через C-интерфейсы сложно, а поэтому давайте закладываться в публичных интерфейсах на недокументированное поведение.
Ало, ало, ало. Вы о чём говорите ребята? Вот документ, там всё стандартизовано и описано. Его поддерживает и GCC и ICC и новомодный Clang…

Казалось бы — а причем здесь вообще стандарт, если сохранение бинарного представления — это забота компилятора?
Притом, что изменения в стандарте могут быть несовместимы с бинарным представлением компилятора, однако. Пример — как раз C++11 и строки: до C++11 строки могли использовать COW-оптимизацию, а в C++11 появились новые требования, которые поставили эту оптимизацию «вне закона». В результате — несколько лет задержка со внедрением новой версии C++.
А вот в мире Linux и Unix — всё было совсем по другому.

Ну…
Например из man gcc (секция про -fabi-version):


Version 1 is the version of the C++ ABI that first appeared in G++ 3.2.

Version 2 is the version of the C++ ABI that first appeared in G++ 3.4, and was the default through G++ 4.9.

Version 3 corrects an error in mangling a constant address as a template argument.

Version 4, which first appeared in G++ 4.5, implements a standard mangling for vector types.

Version 5, which first appeared in G++ 4.6, corrects the mangling of attribute const/volatile on function pointer types, decltype of a plain decl, and use of a function parameter in the
           declaration of another parameter.

Version 6, which first appeared in G++ 4.7, corrects the promotion behavior of C++11 scoped enums and the mangling of template argument packs, const/static_cast, prefix ++ and --, and a
           class scope function used as a template argument.

Version 7, which first appeared in G++ 4.8, that treats nullptr_t as a builtin type and corrects the mangling of lambdas in default argument scope.

Version 8, which first appeared in G++ 4.9, corrects the substitution behavior of function types with function-cv-qualifiers.

Version 9, which first appeared in G++ 5.2, corrects the alignment of "nullptr_t".

Version 10, which first appeared in G++ 6.1, adds mangling of attributes that affect type identity, such as ia32 calling convention attributes (e.g. stdcall).

Version 11, which first appeared in G++ 7, corrects the mangling of sizeof... expressions and operator names.  For multiple entities with the same name within a function, that are
           declared in different scopes, the mangling now changes starting with the twelfth occurrence.  It also implies -fnew-inheriting-ctors.

Version 12, which first appeared in G++ 8, corrects the calling conventions for empty classes on the x86_64 target and for classes with only deleted copy/move constructors.  It
           accidentally changes the calling convention for classes with a deleted copy constructor and a trivial move constructor.

Version 13, which first appeared in G++ 8.2, fixes the accidental change in version 12.

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

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

То есть не все так однозначно, если хочется быть уверенным в надежности работы программы
С надёжностью работы вообще проблем нет. То, что вы тут накопали — это хитрые случаи name manglingа.

Оно влияет только лишь на имена. А почему я про специалистов по марксизму-ленинизму вспомнил — так это потому что вы привели огромную цитату (и даже под спойлер не спрятали), которая включает в себя один раздел из man gcc — но при этом опустили ровно одну строчку. Последнюю. А что ж в этой строчке написано-то?

А вот что:
See also -Wabi.

И что ж такого интересного вы можем увидеть в раздере про -Wabi? А вот что:
-Wabi (C, Objective-C, C++ and Objective-C++ only)
Warn when G++ it generates code that is probably not compatible with the vendor-neutral C++ ABI. Since G++ now defaults to updating the ABI with each major release, normally -Wabi will warn only if there is a check added later in a release series for an ABI issue discovered since the initial release. -Wabi will warn about more things if an older ABI version is selected (with -fabi-version=n).

Там ещё много чего написано — но вообще вся секция -Wabi посвящена описанию того, как линковать версии C++-кода с разными -fabi-version (и, возможно, собранные разными компиляторами).

А буквально следующая за вами процитированной секцией — это секция -fabi-compat-version=n… тоже с этим связанная.

То есть не все так однозначно, если хочется быть уверенным в надежности работы программы, и между частями кода используется C++ ABI, я бы все одной версией g++ собрал, на всякий случай.
Ну если у вас есть исходники от всех компонент, то да, возможно. Но так-то нередки случаи, когда не только части приходится разными версиями собирать, но и разными компиляторами (g++, clang++, icc). И это всё полностью поддерживается, а не как нам тут вещают… про «недокументированное поведение»…
С надёжностью работы вообще проблем нет. То, что вы тут накопали — это хитрые случаи name mangling

Э… "name mangling" такая же полноправная часть ABI как и все остальное. И то что приложение не запуститься пожаловавшись на ненайденный символ или одна из функций отвалилась, потому при вызове функции не удалось подгрузить плагин такая же проблема как и падение программы из разного размера объектов или их разного выравнивания (кстати там и выравнивание упоминается, а не только mangling).

Именно потому и существует опция -Wabi, способная на это отреагировать. И она настроена сегодня, по умолчанию на поддержку совместимости с GCC 5.x не случайно: из-за того, что C++11 поломал поддержку обратной совместимости как раз переход с GCC 3.x/4.x на GCC 5+ несколько затруднён.

Но если вам нужно — то можно установить -Wabi=2.

Что касается выравнивания, то проблемы действительно возможны, но в одном, очень редком случае — при использовании decltype(nullptr)std::nullptr_t это не относится) — то только на процессорах, отличных от x86 и arm.

Что скорее говорит о дотошности разработчиков, чем о вероятности реальных проблем: в MSVC я видел куда большие отличия между сервис-паками, чем у GCC между версиями 3.4 и 9.2…
Вы сейчас описали ситауцию в Windows. А вот в мире Linux и Unix — всё было совсем по другому.
Ну да, потому что там, грубо говоря, на всю экосистему один компилятор и одна библиотека, можно упарываться от души.

Вот документ, там всё стандартизовано и описано
Речь не столько о calling convention или там virtual table layout (это всё десятилетиями прекрасно работает в том же COM), сколько о структурах данных.
Пример: std::vector можно реализовать как { BlockBeginPointer, DataEndPointer, BlockEndPointer }, а можно и как { BlockBeginPointer, DataSize, BlockSize }. Документ этого не оговаривает, стандарт этого тоже не оговаривает. Ваш пример с COW сюда же.

И вот именно повторения этой ситуации (затягивание внедрения новой версии C++ лет так на 5-7) комитет и решил избежать…
… и теперь каждые 3 года мы имеем новый стандарт с новыми способами выстрелить себе в ногу, потому что старые расширять нельзя из-за этого вашего ABI, как и баги исправлять.

Пройдет ещё 10 лет и в стандарте появятся какие-нибудь sane_regex, fast_unordeded_map, mutable_initializer_list и т.п. потому что прогресс неизбежен, но то, что есть — священная корова. Не трожь, а то GCC сломается.

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

«There should be one-- and preferably only one --obvious way to do it» — это, увы, не про C++.
Ну да, потому что там, грубо говоря, на всю экосистему один компилятор и одна библиотека, можно упарываться от души.
А вот давайте не рассказывать сказок? На обычном Linux у вас четыре компилятора (GCC, Clang, ICC и PGI), на AIX — есть IBM XLC++, на SunOS — Oracle C++. Собственно бо́льшая часть компиляторов в списке — это как раз компиляторы под Linux и Unix, не под Windows.

Пример: std::vector можно реализовать как { BlockBeginPointer, DataEndPointer, BlockEndPointer }, а можно и как { BlockBeginPointer, DataSize, BlockSize }. Документ этого не оговаривает, стандарт этого тоже не оговаривает.
А вот это уже как раз так как вы сказали: стандартная библиотека — часть системы, потому как она устроена — так и будет.

«There should be one-- and preferably only one --obvious way to do it» — это, увы, не про C++.
Да, это про язык у которого есть три способа разбора командной строки (getopt, optparse и argparse) — и это несмотря на не так давно устроенный большой %$%@#ц, когда они потратили 10 лет на то, чтобы сделать несколько дастаточно бессмысленных изменений в языке…

А через 20 лет 80% стандартной библиотеки окажется морально устаревшей, но будет висеть мертвым грузом, ибо совместимость.
Это нормально. Есть языки, которые считают, что ломать совместимость это не больно и не страшно (скажем D) — ну… у них отличный язык, замечательная системная библиотека и очень мало пользовать. Всё, что про них нужно знать: не использовать. Никогда и нигде.

А есть языки, где «80% стандартной библиотеки морально устарела»… но ими люди пользуются — и среди них можно выбирать.

Как мы видим Python и C++ — относятся именно туда (чтобы там ни говорили про дзен разработчики первого).

P.S. А есть ещё и такой фактор: если у вас системная библиотека, где 80% — legаcy, то вы тартите это 80% места на диске впустую. Но если у вас каждая динамическая библиотека несёт в себе свою копию библиотеки C++, то при десятке библиотек — 90% места тратится впустую! А 90% — это таки хуже, чем 80%…
А вот давайте не рассказывать сказок?
«Грубо говоря». Они, несомненно, есть, но какова их рыночная доля?

это про язык у которого есть три способа разбора командной строки
«А у вас негров линчуют»? Я далёк от мнения, что Python идеален и не привожу его в пример, но идеи в Zen вполне здравые. Лучше перенимать у других хорошее, чем плохое, не так ли?

А есть языки, где «80% стандартной библиотеки морально устарела»… но ими люди пользуются
Ну да, the ones people complain about and the ones nobody uses.
Однако, у того же автора есть и другая цитата, про much smaller and clearer language struggling to get out. Рант, в основном, о том, что в языке достаточно легаси на уровне кода, если тянуть за собой еще и бинарное легаси — 'get out' не наступит никогда: в C++23 решили ABI не ломать «с небольшим перевесом», а в 26 решат не ломать единогласно, потому что:
— за 6 лет никто и не почешется подготовиться к этому ломанию
— кода, верующего в ABI, станет тупо вдвое больше.
«Грубо говоря». Они, несомненно, есть, но какова их рыночная доля?
А на Windows — что? Есть какой-то компилятор, у которого рыночная доля сравнима с Microsoft C++???

Я далёк от мнения, что Python идеален и не привожу его в пример, но идеи в Zen вполне здравые. Лучше перенимать у других хорошее, чем плохое, не так ли?
Да-да-да. «Делай, как я говорю, а не так, как я делаю сам.»

Плохая идея. Да, идеи в Zen звучат неплохо — но если бы они ещё и были бы осуществимы — так это было бы и совсем хорошо.

Я, как бы, привожу Python в пример не потому что я его не люблю, а потому если что даже язык, который приводит приведённую вами фразу в качестве руководства к действию на самом деле ей не следует… значит тому есть какие-то причины — ведь правда?

А про «get out»… поживём, увидим. Мне кажется самая большая проблема C++ — в том, что он сейчас поддерживается «либо всё, либо ничего». Если сделать как-то так, чтобы программа могла явно заказывать опциональные компоненты (а по умолчанию устаревшие шняги были бы недоступны), то это открыло бы путь к изменениям… хотя всё равно непонятно как можно было бы изменить тот же std::string ещё раз, когда на нём уже куча всего построена…
Есть какой-то компилятор, у которого рыночная доля сравнима с Microsoft C++???
Конечно есть — Microsoft C++ предыдущих версий. Каждая со своим рантаймом и своим ABI, вопросами совместимости которого до недавнего времени в MS не заморачивались.

А про «get out»… поживём, увидим
Я внезапно осознал, что был в корне неправ, get out не нужен. Больше легаси — сложнее язык — выше порог входа — меньше конкуренция — работа будет всегда. Ура комитету!
Конечно есть — Microsoft C++ предыдущих версий. Каждая со своим рантаймом и своим ABI, вопросами совместимости которого до недавнего времени в MS не заморачивались.
Ну то есть вся проблема существовала только и исключительно благодаря Microsoft'у. Причём что самом смешное, для себя-то, любимых, они всё сделали правильно.
НЛО прилетело и опубликовало эту надпись здесь
Можете посмотреть на CPython. Много писанины, но не rocket science.
А я бы с интересом посмотрел, зачем вам таскать thread или graph между модулями.
С моей колокольни полная поддержка юникода важнее всего вышеперечисленного. Будем ждать…
А что, до сих пор не завезли? Ууу блин:(
Не завезли и не завезут пока в C++ не появятся опциональные компоненты и эту поддержку нельзя будет объявить опцией.

Потому что там требуются многомегабайтные таблицы, превышающие по объёму всю сегодняшнюю стандартную библиотеку раз так в 10. Причём нужен ещё и стабильный ABI, а разработчики libicu (библиотеки от Unicode Consortium) его разрабатывать отказываются.

Так что даже в планах на C++23 нету. Просто желающих пользоваться — вагон, желающих работу делать — нуль.
желающих пользоваться — вагон, желающих работу делать — нуль.

Почему-то во многих других более молодых языках эту проблему как-то решили. Странно. Тем более странно при вагоне желающих. С сегодняшними успехами краудфандинга не дожно быть проблем и со сбором денег на реализацию. Загадка.
НЛО прилетело и опубликовало эту надпись здесь
Я не знаю языков с полной поддержкой уникода. Где её решили?

Ну там всякие D, Go, Python… Возможно юникод настолько необъятен, что 100% покрытия в языке нет нигде, не знаю. Однако у большинства всё значительно лучше, чем в C++.
НЛО прилетело и опубликовало эту надпись здесь
Однако у большинства всё значительно лучше, чем в C++.
Я сказал, что у большинства значительно хуже, чем в C++.

Искать подстроку в UTF-8 строке можно и функциями из 70х годов (там UTF-8 устроен, тут никакой поддержки не нужно), а если вы, скажем, хотите без учёта регистра искать… ну покажите мне язык, где это можно просто сделать не перепутав «I» и «i» в Турецком языке?

Для справки: в Турецком языке есть две разные буквы — «I/ı» (без точки) и "İ/i" (с точкой). Соответственно «I» и «i» — это разные буквы.

Большинство языков позволяют с юникодом сделать нечто, что работает «часто, но не всегда»… это один из самых худших вариантов: уж лучше пусть оно на тестах упадёт, чем у заказчика…
покажите мне язык, где это можно просто сделать не перепутав «I» и «i» в Турецком языке?

Здесь расписана эта проблема. В случае «I/ı İ/i» один и тот же символ с одинаковым кодом будет конвертироваться по-разному в зависимости от языка (локали). Там же по ссылке написано и что следует предпринять для решения проблемы, но в общем случае, имея символ и не зная язык, не существует однозначного метода преобразования регистра.

Единственное, что я не понял, так это почему в юникоде не сделали для турецкого языка специальные символы «i» и «I», а используют знакоместо из латиницы. Ведь если не пользоваться латиницей для турецких букв, то и проблема уйдёт сама собой.

Забавный случай, но при чём тут язык программирования? Это скорее проблема юникода.
Единственное, что я не понял, так это почему в юникоде не сделали для турецкого языка специальные символы «i» и «I», а используют знакоместо из латиницы.
Потому что Latin-5 и CP857 так устроены и если бы они были устроены иначе, то Турки бы тупо отказались бы переходить на Unicode.

Если не пользоваться латиницей для турецких букв, то и проблема уйдёт сама собой.
Эта проблема уйдёт, но останется куча других. Возьмите немецкий к примеру. Там в телефонных справочниках «Müller, A» идёт перед «Mueller, B», а «Mueller, B» идёт перед «Müller, C». Классно, правда?

Да возьмите даже, возможно, более близкую вашей душе кириллицу: В Украинском языке "І/і" находится где-то в начале алфавита, между «И/и» и "Ї/ї", а в казахском — между «Ы/ы» и «Ь/ь». И? Как прикажете это всё сортировать?

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

Как поддержка юникода устроена в большинстве других языков? 3-4 «главаря» собираются и делают какую-то поддержку, которая как-то, более-менее, работает для языков их нескольких лучших друзей. Как оно работает для монголов или татар — их не волнует. Пресс-релиз издать можно — и достаточно. И потом — костылей навбиваем. Или нет.

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

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

А вот если мы хотим всё это поддерживать…

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

И вот после этого и выясняется, что пользоваться — хочется много кому, разрабатывать пропозал — не хочется никому. Unicode Consortium свою библиотеку, правда, поддерживает… но она в стандарт ну аж никак не лезет, ибо они наразарабатывали ах 60 с лишним версий этой библиотеки и она не имеет стабильного API (то есть какая-нибудь версия 40 от версии 60 может отличаться радикально и быть несовместимой ни в какую сторону).

Так что… желающих пользоваться — вагон, желающих что-то делать… нету. А если нету таких желающих то откуда поддержке в стандарте взяться?

Стандарт ведь не эльфы разрабатывают…
Турки бы тупо отказались бы переходить на Unicode.

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

Как прикажете это всё сортировать?

С сортировкой всё просто понятно, необходимо всякие местные особенности учитывать, здесь уже и локаль имеет смысл, но с этим я не вижу проблемы. Во-первых сортировка это довольно затратный процесс, во-вторых выставить локаль для всех строк одновременно для упорядочения не приведёт ни к неудобствам ни к каким-то серьёзным трудностям. Другое дело — преобразование регистра. Что делать если у меня разные языки? Хранить язык для каждого слова и менять локаль в процессе поиска для каждого слова или для сортировки? Это же бред. То есть так как оно сделано в в юникоде и в С++ сейчас выходит просто неправильно. Ужас ужас.

Это может быть чья угодно проблема, но пока она не будет решена — никакой поддержки юникода в C++ не будет.

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

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

То есть так как оно сделано в в юникоде и в С++ сейчас выходит просто неправильно.
Почему неправильно? Сделано, то, что возможно. Вы же сами нашли ствтью, где написано, что немцы считают, что DUERST и Dürst — это одно слово… а финны так не считают. И при этоми те и другие уверены, что используют латинницу «правильно».

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

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


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

Преобразование регистра это всё-таки тривиальное преобразование, нетривиальным в случае iI его сделали «странные» (мягко говоря) решения с кодировкой. Решается просто путём ввода нового символа. Может есть какие-то другие, не такие очевидные случаи, не знаю… И то что большинство разработчиков игнорирует такие «фичи» юникода, вполне ожидаемо.
Либо у пользователя можно спросить (Windows, скажем, использует один метод для всех имён файлов на диске, хотя для разных дисков могут быть выбраны разные варианты).

Ну так не поможет же, если на диске есть и турецкие и английские названия, потому что преобразование регистра не будет работать либо для английского, либо для турецкого. Хранить ради этого язык для каждого имени файла что-ли? А если два слова в названии, одно английское, другое — турецкое (например файл с переводом). Тогда нужно переключатели языка прямо в строку вставлять (хотя в данном случае проще вставить новые символы)… Что опять же, должно решаться на стороне юникода, потому что язык слова в контексте преобразования регистра это не свойство локали, это свойство слова!!! Хранить язык в локали для преобразования регистров — ошибка юникода. И решения здесь только два — либо хранить язык в виде невидимых переключателей языка в строке, либо в каждом символе. Хранить в локали просто не получится.
немцы считают, что DUERST и Dürst — это одно слово… а финны так не считают

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

Как только вы что-то добавили в стандарт — на это в очень короткое время оказываются завязаны десятки и сотни тысяч библиотек. Править потом что-либо — очень сложно.

Без юникода сегодня почти ничего нельзя ничего сделать, поэтому всё равно десятки и сотни тысяч библиотек завязаны на то или иное решение. И всё равно, рано или поздно стандарт будет принят и эти сотни тысяч библиотек всё равно придётся переделывать. Так вот, лучше будет, если они будут «сломаны» единообразно, то есть можно будет придумать единообразное решение для поломки, которое можно будет переиспользовать в других библиотеках, чем если каждая будет сломана по-своему. И ещё это будет лучше, потому что код у всех будет одинаково «неправилен» и можно будет придумать стандартные решения для всех стандартных проблем стандарта… Поэтому я склоняюсь к тому что всё равно плохой стандарт лучше, чем вообще без стандарта.
Ну то есть все проблемы разрушили и в Windows тоже.

Поздновато, конечно (я бы вот сегодня побоялся выпускать в свет программу, которая Windows 10 1903+ требует), но… лучше поздно, чем никогда!
UTF-8 обратно совместим со старым кодом и заведомо помещается в char: «The character types are large enough to represent any UTF-8 eight-bit code unit (since C++14)». Для экзотических кодировок можно использовать std::codecvt, но зачем, если уже даже в Windows завезли UTF-8?
В C++20 появился char8_t. Интересно, в каких ситуациях следует применять его, а не char?
Если вкратце — во всех возможных ситуациях. Он не alias-ится со всем подряд, из-за чего использование char8_t порождает более производительный код.

Жаль только что он ещё мало где поддерживается стандартными библиотеками

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

Приведите пожалуйста примеры
Вот, например, пара проблем из реальной жизни:
1) Когда одно и то же место в памяти может быть переиспользовано для хранения данных разных типов, а сам тип определяется, исходя из значения какого-то битового поля. Для того, чтобы это корректно работало, приходится делать юнион из соответствующих типов и массива char[], при этом установка битового поля происходит естественным образом через присваивание элемента юниона, а проверку всегда приходится делать через массив char[].
2) Если у меня есть логическая организация памяти в виде, скажем 64-разрядных слов (такой формат данных), а в этих словах могут храниться данные меньших размеров, то единственный корректный способ их извлечь/записать — это через битовые операции с 64-разрядными словами, напрямую работать с этими данными по указателю нельзя никоим образом.
Может, эти примеры и достаточно специфические, но при работе с низкоуровневыми форматами данных подобные проблемы встречаются с завидной регулярностью.
P.S. Я не призываю убрать strict aliasing, в большинстве случаев это действительно полезное ограничение, но можно было подумать и о тех случаях когда он создает лишний геморрой. Можно же, наверное, сделать атрибут [[may_alias]] или что-нибудь в этом роде…
Его, в сущности, только легализовать нужно. В компиляторах поддержка уже есть. Да и даже если нет то почти должна быть: ведь как-то же char/unsigned char/std::byte им нужно «особым образом» обрабатывать?

Так что можете писать proposal.
Правда тут есть вот ещё какая тонкость: какой вообще процент нужд не закрываются std::bit_cast и требуют [[may_alias]]?

Потому что разборка с тегами прекрасно работает через bit_cast.

Я бы, перед написанием пропозала, об этом вначале подумал.
Во-первых, bit_cast только появился в стандарте.
Во-вторых, bit_cast осуществляет преобразование между типами, создавая копию, он не позволяет работать непосредственно с памятью.
А так, естественно, код в приведенном примере будет работать корректно.
Во-первых, bit_cast только появился в стандарте.
Это был бы хороший довод, если бы may_alias там был бы раньше — но его там пока что нет вообще. Ручками bit_cast делается в C++98, там даже фич C++11 не нужно — единственно, что в отличие от «официального» std::bit_cast он в constexpr функциях не работает.

А [[may_alias]] в стандарте нету и ручками он не делается. Так что если его и вводить — то хорошо бы увидеть пример, где недостаточно bit_cast.

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

Лишние копии объектов компиляторы умеют изводить очень хорошо. А вот оптимизировать доступ в память, когда у них нет полной картины того, что происходит — наоборот, с этим всё очень плохо.
Иногда без работы с памятью никак. Что Вы будете делать, если у Вас, например, в структурах данных, которые могут алиаситься, присутствуют атомарные поля?
На самом деле, спор ни о чем — есть много случаев, когда алиасинг вреден и его можно и нужно избегать, есть и обратные примеры.
На самом деле, спор ни о чем — есть много случаев, когда алиасинг вреден и его можно и нужно избегать, есть и обратные примеры.
Разговор как раз именно такой, какой будет в комитете по стандартизации. Не «хацю вот такую фицю», а «хочу то-то и то-то, потому что это нужно для того-то и сего-то».

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

Какую бизнес-задачу вы решаете? Зачем вам это понадобилось?

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

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

Ваш пример я понял — да, это классический NaN-боксинг. Понятно — где, что, как и почему. Понятно что std::bit_cast решает.

Ok, вы говорите, что у вас структуры с алисингом атомарных полей. Причём таких, что std::bit_cast не поможет.

Идея интересная… но зачем? Какая была изначальная задача? Что именно за проблему мы решаем? Как часто она возникает (если она возникает один раз на миллион строк кода всегда можно вкурить ассемблерную вставку и не париться).

А вы все эти вопросы «замять» хотите…
Представьте, например, какую-нибудь сложную lock-free структуру данных… ну, например, дерево, у которого есть разные типы узлов, тип определяется битовым тегом, а дочерние узлы лежат по атомарным указателям… Это только то, что сразу в голову приходит.
Я бы спросил немного про конкретику. Тот факт, что поле у вас является атомарным — совершенно не значит, что его нельзя засунуть в bit_cast.

То что вы описываете — это что-то совершенно фантастическое: у вас по одному адресу лежат два разных типа данных и оба атомарные и доступ к обоим должен быть атомарным… какая-то жуткая химера…

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

По идее для любой работы с utf-8 строками где раньше использовался char.
Еще меньше проблем с "aliasing".

> Комитет по стандартизации C++: Концепты, Корутины, Модули, Internal linkage, Module linkage, Ranges, Constexpr-алгоритмы, noexcept, Контракты, Executors

И по традиции — они опять забыли про лаяй-генераторы:

Члены комитета по стандартизации ISO/IEC C++ выступили с критикой нового стандарта языка

Когда уже у нас будут хотя бы рекуррентные конструкторы?

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

По ссылке прямо написано: Раздел Юмор

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

По секрету: весь текст написан просто путем состыковывания случайных computer science терминов. Но вы ухитрились в нем увидеть важные вещи. Плюсовики такие плюсовики :) Теперь я понимаю, почему эту новость выпилили даже с ЛОР-а на первое апреля.

Спасибо вам за хорошее настроение!

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


Но важными вещами я назвал не их, а "Концепты, Корутины, Модули, Internal linkage, Module linkage, Ranges, Constexpr-алгоритмы, noexcept, Контракты, Executors" из вашего комментария выше.

А что с json/csv/xml? Какие то идеи по поводу сариализаций в них обсуждаются, или все же стоит ждать рефлексию в C++23, а потом в лучшем случае еще года три?

Без рефлексии нормальную сериализацию можно сделать только внешним кодогенератором, а внешнему кодогенератору достаточно и существующего стандарта.


Так что, я думаю что именно так и будет. Сначала рефлексия, потом ещё года три-шесть.

Нативной поддержки этих форматов ждать пока что уж точно не стоит. Рефлексия важнее
Для меня, самым серьезным прорывом в С++ были:
1. Работа с файловой системой.
2. Работа с многопоточностью.
3. Работа в функциональном стиле.

Я ожидал, что наконец добавят поддержку сетевой работы, интегрируют asio в стандартную библиотеку. Из этих мелких фундаментальных инстурментов, понравился rvalue, не хватало его для оптимизации логики. Видимо сетевая работа в 2020 неактуальна для C++, спасибо хоть многопоточность есть из коробки. Подождем до 2030, может добавят.
За то время, что Networking варится в комитете, он стал ощутимо лучше (нормальная поддержка аллокаторов, хорошая реализация одинарного буффера...)

Concepts позволяют сделать Networking ещё лучше. Одна из причин отсутствия Networking в C++20 — не успели доработать для него концепты

Есть много старых классических учебников для начинающих по С++, но я чтото не могу найти учебника для изучения последних версий C++17/20 с нуля, а не с изучения старых версий а потом изучения изменений и улучшений. Может кто нибудь мне подскажет самоучитель на русском? Ещё бы хотелось для студентов изучающих первый язык программирования какой нибудь portable компилятор C++17/20 с лёгкой IDE? который студенты могли бы сразу поставить на Win7/10 и попробовать писать простые программы...

Может кто нибудь мне подскажет самоучитель на русском?

Специализация от Яндекса на Курсере: ru.coursera.org/specializations/c-plus-plus-modern-development
Именно современный C++, без древностей.

Попробуйте среду Code::Blocks (http://www.codeblocks.org/).
ну выучишь ты С++17/20 — толку если не все их еще поддерживают? или встретишь tuple какой-нибудь, который в 11/14 появился… Лучше учить последовательно — тем более ничего не выпилили из того что добавляли.

Почему стандарт продолжает делать вид, что юникод ещё не изобрели? Почему std::string хранит байты, и часто программисты его использует просто под бинарные буфера. Я хочу работать с юникодом в std::string, чтобы отделять байты и буквы как в python3, хочу из коробки, как в каком-нибудь Qt.

Если у вас есть богатый опыт в работе с юникодом и какие-то идеи, то пишите proposal или просто сходите в подгруппу SG16: Unicode. Если есть какие-то замечания к существующим proposal — напишите мне, посмотрим что можно сделать
Да нет у него опыта. 99% ноющих про отсутствие поддержки юникода вообще не в курсе того, как устроена работа с многими языками.

Им хочется чтобы русский (тайский, вьетнамский, нужное подчеркнуть) язык «из коробки» поддерживался — и всё.
Я хочу работать с юникодом в std::string, чтобы отделять байты и буквы как в python3
Вот уж чего не нужно — так это «ужаса летящего на крыльях ночи», как в Python3. Где в результате даже имя файла — это не строка.

хочу из коробки, как в каком-нибудь Qt.
Ну дык напишите proposal — а вам его раскатают. И может, лет за 5-10, вы и сделаете что-то, что всех устроит.

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

Вот тут это уже обсуждали.
НЛО прилетело и опубликовало эту надпись здесь
Ну можно два строковых типа сделать Ansi и Wide, как в Дельфи уже 10 лет назад сделали.

И как это поможет?
Юникод намного обширнее и хуже, чем "шестнадцатибитный wchar" UCS-2 и UTF-16, к которому многих приучили в Microsoft. И он даже довольно редко так используется, почти везде данные передаются в UTF-8.

Это да, промахнулись в Дельфи в своём время, надо было UTF-8 вводить. Если его ввести как Wide, то уже будет облегчение.
> А я не хочу, потому что это медленно, и платить за unicode-aware-разбор строк при работе с голым ASCII мне бы не хотелось.

То есть, вы не видите разницу между строкой и массивом байт?

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

Эта историческая несуразность, привнесенная из Си, где массив и строка — это одно и тоже, достало уже всех. Думали, что в std::string решат проблему, так нет же, находятся люди, которым only ASCII подавай. Ждем, когда это поколение таки уйдет со сцены.
Ждем, когда это поколение таки уйдет со сцены.
Не дождётесь. Я знаю людей, которым и 20 лет нет — и которые, тем не менее, не разделяют вашей религии:
Строки должны быть строками.

Я вижу последствия «веры в строки» каждый день в Python 3 и вижу сколько боли это всё доставляет там. Причём в Python 2 проблемы были — но их было меньше и решать их было проще.

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

P.S. Заметьте что, скажем, в Go — строки это тоже последовательность байт и это никого особо не напрягает. А язык новый и имеет в стандартой библиотеке и JSON и HTTP и кучу всего ещё.
P.S. Заметьте что, скажем, в Go — строки это тоже последовательность байт и это никого особо не напрягает.

Кое-кого всё же напрягает

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

И поэтому вы хотите задащить Unicode в C++? В язык, где всё-всё-всё безопасно и надёжно и важно защититься от ошибок?

Что вы такое курите и почему не делитесь? C++ всегда стоял на принципах: «программист знает лучше». Там нет «подушек безопасности». Хорошо или плохо это — другой вопрос… но таковы «правила игры».

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

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

Уж извините.
C++ всегда стоял на принципах: «программист знает лучше». Там нет «подушек безопасности». Хорошо или плохо это — другой вопрос…

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

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

И в итоге в большом проекте с кучей зависимостей имеем зоопарк реализаций одних и тех же сущностей.

> А я не хочу, потому что это медленно, и платить за unicode-aware-разбор строк при работе с голым ASCII мне бы не хотелось.

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

Реально разница между полноценной строкой с поддержкой локалей и массивом байт (с набором пары методов для удобства) есть только для GUI-ориентированных языков/фреймворков. Вот в JS'ах или Qt'ах этому самое место. А если у вас задача «взять N байт текста из базы и пихнуть в http-ответ as is», вас совершенно не должно интересовать, какой именно кодировке принадлежат эти N байт.

Между прочим, даже для «однобайтных строк» и «массивом байт» существуют различия, например, в API.
Даже в GUI всё не так однозначно. Когда вы получаете этот самый «http-ответ», то вы бы должны его как-то отрендерить… а для этого как бы распарсить… а для этого, «в правильных языках» — указать кодировку… которая, в реальном http-ответе живёт внутри этого документа!

Или имена файлов те же. Вы обратили внимание, что они, в том же прославляемом чуть выше Rust'е не являются строками? Потому что так Windows API устроен.

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

При этом, заметьте, тот же Python 3 (сделанный, как бы, ради того, чтобы в языкее были «правильные» строки) в этом месте сделал ошибку и разработчикам потребовалось восемь лет на то, чтобы её признать и исправить.

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

Ещё один Python 3 нам не нужен. Одного раза достаточно.
И вот в этом мире кто-то хочет утверждать, что иметь в языке «строки как произвольную последовательность байт» — это неправильно и ненужно? Нет уж, спасибо.

Строки нужны разные. И массивы произвольных байт, и представленные в формате UTF-8, и в формате UCS-2, и даже UCS-4. Было бы хорошо, если бы язык поддерживал строки в разных форматах и предоставлял функции для их преобразования друг в друга.


Потребность в юникоде есть, и она огромна. Простейший сценарий — файловое API. Например, в стандартной библиотеке C++ предусмотрено только использование последовательности байт в качестве имён файлов, и этим она очень сильно ограничена.

Потребность в юникоде есть, и она огромна.
А вот поддержка… добра в стандартной библиотеке — отсутствует.

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

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

Так что… На Windows используем WTF-8, на всех остальных OS — последовательность байт (как у них, собственно, всё на самом деле и реализовано). Всё. Но единого байта в API стандартной библиотеки добавлять не нужно, все изменения можно легко сделать «под капотом».

Благо делать это нужно только на Windows, где Microsoft всё равно произвольно ломает API в каждом релизе Visual Studio.

Ну то есть не язык — для программиста, а программист — для языка?
Почему нельзя просто добавить подержку std::wstring в io в стандартную библиотеку? Тогда и Microsoft перестанет изобретать своё API.

Почему нельзя просто добавить подержку std::wstring в io в стандартную библиотеку?
Потому что совершенно неясно как оно должно работать на Android, GNU/Linux, и прочих всяких MacOS.

Тогда и Microsoft перестанет изобретать своё API.
Дык цель же не в том, чтобы Microsoft перестал чего-то там изобретать, а в том, чтобы можно было писать переносимый код.

С помощью моего предложения писать переносимый код можно, с помощью вашего нельзя… так какой в нём смысл?
Потому что совершенно неясно как оно должно работать на Android, GNU/Linux, и прочих всяких MacOS.

В случае Linux/Android — преобразовывать внутри вызова в UTF8/WTF8, в случае Windows/MacOS — вызывать сразу нативные системные функции, работающие с UTF16/UCS2.


Java как-то справляется, и весьма успешно. Люди используют родной String и не изобретают велосипедов.


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

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


С помощью моего предложения писать переносимый код можно, с помощью вашего нельзя… так какой в нём смысл?

Почему нельзя-то? Я хочу просто писать


fstream::open(L"Жопа носорога.txt", ...)

а не использовать костыли в виде:


fstream::open(L"Жопа носорога.txt"_u8, ...)

особенно с учётом, что стандарт вообще никак не регламентирует кодировку. Может, там CP1251, а не UTF8?

В случае Linux/Android — преобразовывать внутри вызова в UTF8/WTF8
Ну преобразуйте, к примеру, L"\Uffffffff\fffffffe" — а я на вас посмеюсь.

Java как-то справляется, и весьма успешно.
Ну, насчёт «весьма успешно» — это бабушка надвое сказала. Но да… справляется.

Люди используют родной String и не изобретают велосипедов.
Ну то есть Java вы тоже не знаете. Да — они-таки изобретают велосипеды — и именно потому что «родного String» недостаточно.

Сейчас переносимость достигается за счёт урезания функционала до некоторого общего подмножества. А это неправильно.
Ещё ifdef'ы можно. Тоже вполне работает.

Почему нельзя-то? Я хочу просто писать
Ну вот и добрались, наконец, до сути. Вы не хотите «поддержки Unicode». Вы хотите, чтобы ваш Windows-only код, вдруг, без всяких усилий с вашей стороны, стал переносим. Причём так, чтобы на Windows всё работало хорошо, а на других платформах — как-нибудь.

А почему, собственно комитет должен это делать? Microsoft совершенно сознательно сделал платформу Windows несовместимой со всеми остальными. В рамках классического EEE: Windows 1.0-3.x — всё как у всех, Windows 95/NT (и потомках) — как у всех и «ещё лучше», Windows CE — «все OS, кроме Windows — неправильные». В рамках той стратегии — и отказ от поддержки C99/C11.

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

Проигравший войну должен за неё заплатить. Особенно если он и был зачинщиком войны…
Ну то есть Java вы тоже не знаете. Да — они-таки изобретают велосипеды — и именно потому что «родного String» недостаточно.

Ну нет, Path никакого отношения к проблемам кодировок не имеет. Он создан для работы операций с путями, таких как startsWith или getParent.

Ну преобразуйте, к примеру, L"\Uffffffff\fffffffe" — а я на вас посмеюсь.

Здесь в любом случае придётся страдать, потому что множество допустимых имён файлов не совпадает с множеством допустимых представлений строк в формате UTF8 или UTF16.


Ну то есть Java вы тоже не знаете. Да — они-таки изобретают велосипеды — и именно потому что «родного String» недостаточно.

Это не велосипед, а выделение специфичного функционала в отдельный класс. Все равно, что создать класс Point<T> вместо tuple<T, T>.


И раз критикуете — тогда предлагайте. Как бы вы реализовали подобный функционал в C++? Или вы считаете, что ему вообще не место в стандартной библиотеке?


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

Именно так. Слишком популярная платформа, чтобы с ней воевать.


Причём так, чтобы на Windows всё работало хорошо, а на других платформах — как-нибудь.

Почему "как-нибудь"? Простое преобразование wstring в UTF-8 — это "как-нибудь"?


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

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

И раз критикуете — тогда предлагайте. Как бы вы реализовали подобный функционал в C++? Или вы считаете, что ему вообще не место в стандартной библиотеке?
Я уже сказал всё что я думаю по этому поводу. Стандартная поддержка локалей в стандартной библиотеке C++ уже достаточна для того, чтобы сделать работу с именами файлов переносимой на любую современную OS.

То, что Microsoft, вместо этого, решил насоздавать своих личных костылей — проблема Microsoft, а не C++.

Почему «как-нибудь»? Простое преобразование wstring в UTF-8 — это «как-нибудь»?
Да — это «как-нибудь». Потому что на других платформах wchar_t не такой, как на Windows, всё равно. Потому программы, рассчитанные на Windows будут работать плохо. То есть или нужно ломать C++ ABI (и делать его несовместимым с C), либо у вас будут приколы типа уже упомянутого L"\Uffffffff\fffffffe".

И даже если вы, в угоду совместимости с Windows, сломаете сложившийся API, откажетесь от совместимости с C и сделаете wchar_t 16-битным — «щастя» вы всё равно не достигните, так как в этом случае уже не все имена файлов станут представимы на GNU/Linux (хотя все — на Windows).

Нафиг-нафиг. Хотите писать переносимые программы — Microsoft дал такую возможность. Не хотите — ну значит и не хотите, можете продолжать использовать любые расширения MSVC, причём тут стандарт?

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

На это никто «не вёлся» и тогда, когда Windows — была самой популярной OS, смысл «вестись» на подобные вопли сейчас, когда это уже давно не так?

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

А если кто-нибудь (например Rust) и вытеснит C++ — то это точно случится не из-за того, что комитет по стандартизации отказался странные расширения, которые за десятки лет сподобился реализовать только один вендов, в стандарт тянуть.
Я уже сказал всё что я думаю по этому поводу. Стандартная поддержка локалей в стандартной библиотеке C++ уже достаточна для того, чтобы сделать работу с именами файлов переносимой на любую современную OS.

Ещё раз: от локалей нужно избавляться. Это костыль, который в современном мире не нужен.


В C++ было сделана правильная вещь — создан filesystem TS, однако, в духе C++, начато за здравие, а кончено за упокой. Вы можете создать объект std::experimental::filesystem::path (прям как "велосипед" в Java), но чтобы открыть файл, придётся все равно конвертировать его в байтовую строку. А ведь достаточно было просто сделать, чтобы std::ifstream::open принимал на вход этот объект, а не строку.


Потому что на других платформах wchar_t не такой, как на Windows, всё равно.

А это уже вопрос реализации стандартной библиотеки на конкретной платформе. Не нравится wstring с непонятным размером wchar_t? Есть u16string, u32string. Правда, u8string нет — обидно, опять всё в духе C++.

Ещё раз: от локалей нужно избавляться. Это костыль, который в современном мире не нужен.
Нифига ж себе заявленьице. Все люди в мире уже на одном языке заговорили? Так-то, в локалях ест ещё и такая вещь, как std::messages, например.

Вы можете создать объект std::experimental::filesystem::path (прям как «велосипед» в Java), но чтобы открыть файл, придётся все равно конвертировать его в байтовую строку. А ведь достаточно было просто сделать, чтобы std::ifstream::open принимал на вход этот объект, а не строку.
Ну вот это уже — в принципе разумная идея. Не могу сказать, что мне она нравится… но да, сказав A и заведя std::filesystem::path (который уже давно не experimental) разумно сказать и B.

Но ведь это не сделает ваши уже написанные Windows-программы переносимыми…

Правда, u8string нет — обидно, опять всё в духе C++.
Как это нету. В списке из стандарта оно есть. Трясите разработчиков вашего компилятора.
Нифига ж себе заявленьице

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


Но ведь это не сделает ваши уже написанные Windows-программы переносимыми…

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

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

Вот только в POSIX вообще никак не регламентировано использование юникода. Согласно POSIX, имя файла — произвольная последовательность байт, ограниченная по длине. Разделитель пути — '/', единственный запрещённый символ — '\0'. На этом, собственно, всё.


А дополнительно накладываемое системой ограничение на использование UTF-8 для имени файла — это уже не POSIX.


Угадайте, какая единственная популярная ОС намеренно не поддерживает posix

Исторически сложилось. Ещё с CP/M этот шлейф тянется.


а заставляет вас жрать собственное отвратительное API?

Так языки программирования и призваны оградить программиста от этого API, разве нет?

Вот только в POSIX вообще никак не регламентировано использование юникода. Согласно POSIX, имя файла — произвольная последовательность байт, ограниченная по длине.
хватит повторять одно и то же, скажите хоть единожды чего в этом плохого
Исторически сложилось
«исторически сложилось» это когда они по каким-то причинам не могут поддерживать posix API. МС же не хотят это делать.
Так языки программирования и призваны оградить программиста от этого API, разве нет?
У разных ЯП может быть разное призвание. Для питона какая-то там конвертация строки не имеет значения, а с++ построен на zero-cost абстракциях, которые здесь невозможны. Комитет, в отличие от МС, это исправить не может.
хватит повторять одно и то же, скажите хоть единожды чего в этом плохого

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


«исторически сложилось» это когда они по каким-то причинам не могут поддерживать posix API. МС же не хотят это делать.

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


а с++ построен на zero-cost абстракциях, которые здесь невозможны

То есть, проблему вообще решать не надо?

Тем, имеется потребность в использовании юникода в именах файлов. А стандартов, предлагающих решение этой проблемы, нет.
множество «юникод» является подмножеством «абстрактной последовательности байт».
Изначально не могли поддерживать, ну а дальше — совместимость с предыдущими версиями и жуткий legacy.
им не впервой расширять свой API, поэтому «совместимость» — не аргумент, а их легаси должен оставаться их же головной болью
То есть, проблему вообще решать не надо?
я уже несколько раз написал — надо решать. А решать проблемы винды может только МС, вот пусть они и решают
множество «юникод» является подмножеством «абстрактной последовательности байт».

Это и проблема. Не любое имя файла является валидным.

Это и проблема. Не любое имя файла является валидным.
Вы, наверное, недописали чего-то. Не любое имя файла является валидной Unicode-строкой? Да, так большинство современных OS устроены. Дальше что?

Да, не любое имя файла является валидной Unicode-строкой. И именно это и мешает использовать Юникод.

Ну так ваше предложение проблему не решает ни разу. Оно ни в Windows не является валидной Unicode-строкой, ни в Android или GNU/Linux и ни в случае с 8-битовыми, ни в случае с 16-битовыми символами… Так какую, собственно, задачу решает ваше предложение и почему нельзя решить эту же задачу иначе?

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

Проблемой это становится, если вы решаете показать это имя пользователю… Но у меня на диске 400000 файлов, а в папке с документами — порядка 10000, так что подавляющее большинство имён файлов пользователю никогда и не показываются…

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


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


Моё предложение: добавить в стандартную библиотеку возможность использовать u8string, u16string, u32string для имён файлов.


При использовании локали UTF-8 строка u8string будет использоваться без перекодирования. При использовании других локалей будет выполняться преобразование.
В случае Windows при этом возможно использование расширенного API, не привязанного к локалям.

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

Моё предложение: добавить в стандартную библиотеку возможность использовать u8string, u16string, u32string для имён файлов.
Мне это предложение не кажется уж очень полезным. Однако ваше другое предложение, которое вы высказали мельком — разрешить открывать потоки с использованием std::path выглядит более разумным: у нас std::path уже есть, с локалями он уже работает — так давайте все эти сложности вынесем туда.

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

При использовании локали UTF-8 строка u8string будет использоваться без перекодирования. При использовании других локалей будет выполняться преобразование.
Если я правильно понимаю std::path с u8string так должен работать уже сейчас…
На мой взгляд, самой большой проблемой является то, что человек воспринимает имя файла как последовательность графем, а не code units или code points. И в разных окружениях (ОС/локаль/конкретная библиотека, etc.) одна и та же последовательнось графем может быть представлена в виде разных последовательностей code points. И без поддержки эквивалентности между кодовыми последовательностями корректной работы не добиться. Более того, у нас в практике был случай, когда файл с русским именем, содержащим букву «й», в одном окружении был заархивирован, а в другом его по имени не удалось извлечь из архива ровно по этой причине. В любой Unix-системе в каталоге легко могут быть четыре разных файла, которые для пользователя будут все выглядеть как «мой_файл» :) И попробуйте неподготовленному человеку объяснить, что это разные имена :)
В любой Unix-системе в каталоге легко могут быть четыре разных файла, которые для пользователя будут все выглядеть как «мой_файл» :)
Не в любой. macOS всё нормальзует.

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

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

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

Безусловно.
То есть если мы запишем файл с именем «мой_файл», где в строке будет буква «й» без декомпозиции, потом прочитаем директорию в виде списка строк, то мы в этом списке исходную строку «мой_файл» не найдем?
Да, именно так, насколько я знаю.
P.S. Самое занятное, что даже попытка договориться об использовании какой-либо из нормализованных форм не поможет, так как есть много примеров эквивалентных с точки зрения Unicode строк, которые имеют разные нормализованные представления. Например, к этому приводит использование лигатур.
Лигатуры обычно можно различить визуально.

Но вообще меня удивляет что «неподготовленный человек» вроде как понимает что «мой_файл» и «мoй_файл» (где «o» в одном случае русская, в другом латинская) — это разные имена, а для разных видов «й» — это проблема.

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

Но и эта дискуссия тоже к C++ отношения не имеет.
Да, не любое имя файла является валидной Unicode-строкой. И именно это и мешает использовать Юникод.
а при чем тут с++?
Изначально не могли поддерживать
Ну сколько можно врать, а? Нет — могли, но не хотели. В 80е вообще нельзя было некоторым министерствам в США продавать не POSIX-сертифицированную OS и потому уже Windows NT 3.1 (самая первая версия) включала в себя поддержку POSIX — ровно в объёме, требуемой сертификацией. При этом некоторые ключевые подсистемы POSIX (например работа с сетью и GUI… в операционной системе называющейся Windows, да) были сознательно не реализованы.

Ну а когда разработчики «почему-то» не захотели под это писать софт — Microsoft пролоббировал отмену соотвествующих правил.

То есть, проблему вообще решать не надо?
Проблему решать нужно, но её должен решить тот, кто её и создал — Microsoft. Собственно он её и решил, только вам это решение, почему-то, не очень нравится.

Ну C++ — это не «золотой червонц, чтобы всем нравиться».

std::ifstream::open уже принимает std::filesystem::path начиная с C++17

Именно так. Слишком популярная платформа, чтобы с ней воевать.
«миллионы мух не могут ошибаться» (с)
Почему «как-нибудь»? Простое преобразование wstring в UTF-8 — это «как-нибудь»?
у пользователя вообще не должно быть потребности использовать двухбайтные кодировки. Пусть лучше винда под капотом перекодирует, а весь апи будет однобайтным. Тем более что даже сейчас так можно компилировать
Войну в итоге проигрывает C++, становясь нишевым языком и уступая место более конкурентоспособным языкам.
… since 1981
у пользователя вообще не должно быть потребности использовать двухбайтные кодировки.

Сильное заявление. Прям как 640кб памяти хватит всем.


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

Так она так и делает. Просто при использовании однобайтного API результат будет зависеть от текущей локали.

Просто при использовании однобайтного API результат будет зависеть от текущей локали.
Чего, собственно, и достаточно, потому что локаль с поддержкой UTF-8 Windows, с недавних пор, тоже поддерживает.
а почему велосипеды майкрософта, которым давно пора на свалку истории в пользу человеческого utf8, должны иметь отображение в стандартной библиотеке плюсов? Почему бы майкрософту не перестать изобретать своё API прямо сегодня?
Потому что он это уже сделал вчера. К огромному сожалению вот реально почти вчера: As of Windows Version 1903 (May 2019 Update), you can use the ActiveCodePage property in the appxmanifest for packaged apps, or the fusion manifest for unpackaged apps, to force a process to use UTF-8 as the process code page.

Но да, вкручивать поддержку всего этого в C++23 действительно странно: к моменту, когда этой поддержкой можно будет без проблем пользоваться — она станет окончательно бессмысленной…

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

Нельзя менять логику работы уже использующегося API
А никто ничего и не менял. cp65001 — это всего лишь ещё одна кодовая страница. Если программа работает с кодовыми страницами правильно — то она должна и с cp65001 работать без проблем. А если неправильно — то ваша программа никогда и не работала ни с кодовыми страницами, ни с Unicode, её нужно исправить.

Увы, до сих пор слишком много вещей завязано на однобайтовые кодовые страницы. Исторически сложилось. Говно мамонта, которое будет нас ещё долго преследовать.
Оно будет вас преследовать ровно до того момента, пока вы не возьмёте лопату и не расчистите его. Все другие OS начали этим заниматься ещё в прошлом веке — и уже лет 10 назад от «горы» остались маленькие кучки. Microsoft, в своей гордыне, понадеялся без этого обойтись и заставить весь мир плясать под свою дуду… не получилось. Так что… да — настала пора с этим разбираться…

Кодовые страницы — это и есть говно мамонта и костыль. Появились они не от хорошей жизни.


cp65001 — это всего лишь ещё одна кодовая страница

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


Все другие OS начали этим заниматься ещё в прошлом веке

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


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

Отлично. Давайте, например, откажемся от использования ZIP-формата?


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

Ну, Microsoft хотя бы попыталась сделать хоть что-то.

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

Имена файлов в Windows не обязаны быть валидными Unicode-строками.

Все другие OS начали этим заниматься ещё в прошлом веке
Всё ещё хуже: они попросту даже не пытались заниматься этим.
Я говорю про OS, не про ядро сейчас. Старые версии GNU/Linux или даже MacOS не поддерживали Unicode во многих местах. Современные OS — поддерживают. Там где нужно.

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

Ну, Microsoft хотя бы попыталась сделать хоть что-то.
Знаете — иногда лучше не делать ничего, чем делать чушь. Вот Microsoft — сделал чушь.

Да, у них были самые наилучшие побуждения, да, во времена Unicode 1.x казалось, что использование UCS-2 — это «то, что надо»…

Но те времена давно уже прошли, начиная с Unicode 2.0 (а это, так-то, 1996й год, давненько уже) UTF-16 — это такой же самый ужасный набор костылей, что и в случае с UTF-8. Даже хуже: вы не можете использовать существующие, пришедшие из C, функции, а вынуждены весь свой API, фактически, дублировать.

Отлично. Давайте, например, откажемся от использования ZIP-формата?
А что — есть существенно лучшая и, притом, популярная, альтернатива?
Знаете — иногда лучше не делать ничего, чем делать чушь. Вот Microsoft — сделал чушь.

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


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

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

Поэтому расширение API функциями, принимающими на вход строки c 16-битными символами, оказалось логичным.
Что мешало расширить функции, принимающие на вход строки с 8-битными символами? Как, собственно, и сделали все остальные OS?

Я понимаю Java: эти-то ребята попали «как кур в ощип». Сделали язык «красивым и прогрессивным», со строками чисто в UCS-2 без всяких альтернатив… а потом пришлось пристраивать к этому делу костыли.

А вот поведение Microsoft объяснить трудно: в тот момент, когда стало понятно, что UCS-2 «не работает» (а это, напоминаю, 1996й год) самая популярная OS от Microsoft Unicode не поддерживала! Что мешало надстроить костыли над ASCII (то есть перейти на UTF-8), а не над UCS-2 (то есть заставить всех перейти на совершенно новый… и уже с самого начала костыльный… API)?

Притом, что этот переход оказался столь тяжёл, что, как вы сами говорите, не все его «осилили» и за четверть века…
Что мешало надстроить костыли над ASCII (то есть перейти на UTF-8), а не над UCS-2 (то есть заставить всех перейти на совершенно новый… и уже с самого начала костыльный… API)?

Тот факт, что не любая последовательность символов является валидным представлением UTF-8. Вот они и сделали дополнение к API в виде новых функций. Правда, даже чистые Windows-разработчики далеко не сразу это оценили.


Что мешало расширить функции, принимающие на вход строки с 8-битными символами? Как, собственно, и сделали все остальные OS?

Скорее, что мешало сделать простую валидацию UTF-16? Тогда бы и не вылезали проблемы с совместимостью в будущем и WTF-8.


Apple, кстати, тоже поначалу вляпалась в UTF-16. Но потом одумалась и волевым решением перешла на UTF-8. Но то Apple — политика у неё такая, не принимающая legacy.

Тот факт, что не любая последовательность символов является валидным представлением UTF-8.
Так и не любая последовательность 16-битных чисел является валидным представлением UTF-16 — и, тем не менее, огромный процент Windows API принимает на входе именно это.

Apple, кстати, тоже поначалу вляпалась в UTF-16. Но потом одумалась и волевым решением перешла на UTF-8.
Ну вот и Microsoft начал переходить. В 2019м году, конечно, но… лучше позже, чем никогда.

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


в пользу человеческого utf8

Отлично. Дайте возможность работать в C++ хотя бы с UTF8. Сейчас в стандартной библиотеке плюсов вообще ничего нет: только wchar_t, wstring, да и те малофункциональны.

Потому что существуют файловые системы, использующие юникод для хранения имён файлов.
utf8 тоже «юникод», и файловые системы лучше бы переделали на него. В конце концов, utf16 не решает ни одной проблемы относительно utf8, а лишь усугубляет их
Отлично. Дайте возможность работать в C++ хотя бы с UTF8
так в чем вопрос то? Знаете как — реализуйте, пришлите комитету.
Достаточно обговорить как расширить функцию setlocale.

Я так понимаю на последних версиях Winddows должна работать setlocale(LC_ALL, ".cp65001"), так что должно быть несложно поддержать setlocale(LC_ALL, ".utf-8") на всех системах.

Чего, в общем, вполне будет достаточно для написания переносимого кода с Unicode. Хотя, конечно, по-хорошему нужно существенно расширять описание locale (у Microsoft он тоже есть, хотя не знаю насколько он там функционален).

UTF8 — это просто кодировка. UTF8, UTF16 — да какая разница, какое там внутреннее представление?

Большая разница. Функции работы со строками из 70х отлично работают с UTF-8 (даже такие, как strtok), но не работают UTF-16.

Часть, правда, не работают ни с той кодировкой, ни с другое (скажем std::tolower) но тут уж ничего не поделаешь: все эти функции, в общем случае, требуют знания языка, на котором написан текст для правильной работы.
Большая разница. Функции работы со строками из 70х отлично работают с UTF-8 (даже такие, как strtok), но не работают UTF-16.

Сейчас уже не 70-е на дворе. У нас есть C++, шаблоны — почему нельзя использовать более современные решения, а не всеми силами цепляться за совместимость с C?


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

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


То есть полноценный tolower может существовать только в одном варианте: когда на входе и на выходе — строки.

У нас есть C++, шаблоны — почему нельзя использовать более современные решения, а не всеми силами цепляться за совместимость с C?
Сейчас уже не 70-е на дворе. Именно потому что с 70х прошло много времени и было написано не много, а очень много кода, который использует оную совместимость. И если мы всё равно вынуждены городить «костыли», то логично же выбрать такие костыли, которые создадут нам меньше проблем.

Например, «Й» — это зачастую «И» + кратка. Но символ-то один!
Ну как раз если вы «И» превратите в «и», то всё хорошо будет.

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

Эти файловые системы очень популярны, и работать с ними хочется не с помощью костылей, а единообразным образом.
Вы уверены, что поддержка только и исключительно MacOS как «основной платформы» того стоит? Ах, вы про Windows… Нет, к сожалению NTFS не использует Unicode. Она использует некое «творчество по мотивам», которое позволяет (но не требует!) использовать Unicode-имена для работы с файлами. И то же само происходит в GNU/Linux (однако допустимое множество не-Unicode имён у них отличается).

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

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

Какое-то уж слишком сильное утверждение. Не соглашусь с вами.


  1. Десктопный сегмент. Основные ОС — Windows, MacOS. В обеих используются файловые системы с частичной поддержкой юникода. Есть ещё Linux, но его доля пренебрежимо мала, чтобы его считать хоть немного значимым.


  2. Мобильный сегмент. Основные ОС — Android, iOS. В случае iOS используется APFS с поддержкой юникода. В случае Andriod — да, под капотом ФС с хранением имён файлов в виде последовательности байт, но есть нюанс: прямого доступа к API как в десктопном Linux там нет, только через Java-прокладку с принудительной конвертацией в UTF-8.


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



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

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


И решения этой проблемы попросту не существует. Частично проблема решена в Android, MacOS/iOS — для имён файлов можно использовать только UTF-8 (в HFS было UTF-16). Новая система — новые правила.


В Linux и Windows же проблема не решена. В Linux ею вообще не занимались. В Windows имеем WTF-8 вместо UTF-8.

Десктопный сегмент. Основные ОС — Windows, MacOS. В обеих используются файловые системы с частичной поддержкой юникода.
В Windows нет никакой «частичной поддержки Unicode». NTFS поддерживает только UCS-2, что не совпадает с тем, что поддерживают высокоуровневые API.

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

но есть нюанс: прямого доступа к API как в десктопном Linux там нет, только через Java-прокладку с принудительной конвертацией в UTF-8.
Не порите чушь, ей больно. Берёте dataDir и разводите в этой директории всё, что угодно. Никакого отличия от $HOME в GNU/Linux. Многие приложения вообще тащат с собой свою копию libc (понятия не имею зачем, но тащат).

Грабли стандартные: множество байтовых последовательностей, исторически допустимых в именах файлов, не совпадает с множеством байтовых последовательностей, являющихся валидным представлением юникода.
Именно так. Причём так обстоит дело на большинстве современных OS.

В Windows имеем WTF-8 вместо UTF-8.
В Windows, строго говоря, имеем гремучую сместь из UCS-2 (в NTFS) и UTF-16. Но да, это всё можно перекодировать, при необходиомсти, в WTF-8.

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

Частично проблема решена в Android
Не решена. Да, dataDir гарантированно вписывается в Unicode (а на самом деле даже в ASCII). А вот что там «ниже» — это уже как приложение сделает, так и будет.
Qt не отделяет даже буквы: https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Multilingual_Plane. UTF-16 внезапно оказалось недостаточно и появились суррогатные пары. К тому же, что делать с эмодзи, которые могут состоять из нескольких юникод-символов?
Вот даже интересно: какую реальную задачу можно легко решить с UCS-4, но сложно с UTF-8? Длина строки в символах — одинаково сложная задача в обоих кодировках, потому что символы могут быть модификаторами предыдущего, а для графического размера надо отрисовывать, потому что символы могут быть разной ширины.
А в каком десятилетии extensions к классам завезут? Такая же простая но крайне полезная фича
надеюсь ни в каком, расширения — очень вредная фича. Если её завезти в с++, любой класс внезапно перестанет быть завершенным и контекстуально независимым. А плюсы и так далеко не самый легко читаемый/анализируемый язык.

Очень жаль, значит пока expected-lite наше фсё :)

Есть ещё Boost.Outcome

Есть, но не во всех проектах хочется видеть Boost в зависимостях.

extern «Cpp<2b» { /* your ABI dependent code here */ }
чем не выход?
Это большое усложнение языка ради небольшого выигрыша. Скорее уж надо пойти по пути введения в стандарте термина, обозначающего «при следующем сломе ABI». Тогда можно говорить «при следующем сломе ABI конструктор копирования tuple становится тривиальным для тривиальных типов»

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

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

С предложенным выше подходом можно просто писать «Реализовали C++23», что звучит намного лучше чем какая-то экспериментальная поддержка

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

Вы не видите, а вот маркетинговые отделы видят. И в очень большом количестве компаний деньги на переход на C++23 найдутся, а вот на «экспериментальную поддержку» — нет.

Да, я знаю, что в мире JavaScript — это нормально. В мире C++ — нет. Подавляющее большинство компаний «сидели на жопе ровно» и не трогали ничего из C++11 пока разработчики GCC не объявили «всё, поддержка C++0x более не является экспериментальной… можно пользовать» в GCC 5.

Может быть в каких-то компаниях по каким-то причинам к формальному статусу поддержки стандарта компилятором относятся очень серьёзно, но я не понимаю, какие это могут быть причины.
Причина проста: компилятор может потребоваться заменить (скажем с MSVC на ICC) или просто откатить (по тем или иным причинам — скажем новые версии GCC перестали поддерживать нужную вам фичу или платформу).

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

Ладно ещё если нужной вам фичи в разных компиляторах нет — это ещё полбеды. А если есть, но она реализована по разному или с ошибками?

Те же designate initializers возьмите: до clang 10 они банально небезопасны!

Да, в основном это касается крупных проектов и огромных компаний… но поскольку они и являются главными спонсорами развития C++… то они заказывают музыку.
Те же designate initializers возьмите: до clang 10 они банально небезопасны!

Приведите ссылочку, пожалуйста.
Хочу знать, с какой стороны ждать грабли.
Я использую для заполнения структур (например, вот этот файл можно почти безболезненно переименовать в cpp), и пока с проблемами не сталкивался. Зря использую?..

забыл ссылку добавить, извините:
дескрипторы USB создаются в compile-time и прямо в таком виде передаются "наружу"

Хочу знать, с какой стороны ждать грабли.
Со стороны нарушений стандарта. Валидный C++20 код вроде бы все версии clang'а компилируют нормально — но если вы перепутаете порядок, то у вас может быть беда. И, главное, никакой диагностики (она в clang 10 появилась).

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

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

Я использую для заполнения структур (например, вот этот файл можно почти безболезненно переименовать в cpp), и пока с проблемами не сталкивался. Зря использую?..
Это C. В C нет конструкторов и, соответственно, нет проблемы с порядком инициализации.

Мой косяк — не уточнил, то они небезопасны в C++. В C — вроде всё в порядке во всех версиях, где они поддерживаются. Думал из контекста понятно: в C они больше 20 лет назад появились, там они, конечно, экспериментальной фичей не являются.

Спасибо, изучу.
И я неправильно выразился: по ссылке примеры использования библиотеки. Я что-то очень похожее пишу в cpp-файлах (общедоступной ссылки не дам), вроде б работает...

Собственно как я уже и написал: всё сводится к порядку. C++20 и GCC требуют, чтобы он совпадал с порядком описания полей типа, Clang — не требует. Причём -Werror=reorder-init-list Clang 10 только понимает, у более ранних этой диагностики нету.

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

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

Обычная осторожность.

На минутку, я не предлагал использовать ещё не стандартизованные фичи. Только удивлялся, как проект может отказываться от полезной фичи только из-за того, что поддержка стандарта, в который она входит, носит статус экспериментальной (как C++17 в gcc версий 6-8).


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

На минутку, я не предлагал использовать ещё не стандартизованные фичи.
А где вы тут увидели «нестандартизованные фичи»? Designated initializers — это часть стандартов C99 и C++20.

Только удивлялся, как проект может отказываться от полезной фичи только из-за того, что поддержка стандарта, в который она входит, носит статус экспериментальной (как C++17 в gcc версий 6-8).
Ну дык, блин, об этом и речь! Пока фича экспериментальная — ошибки в её реализации мало кого волнуют. Их могут годами править! А на вопрос «какого фига» ответ будет «фича экспериментальная, чего вы тут кипятитесь».

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

Он не был частью какого-либо стандарта C++ на момент выхода clang 9, о проблемах в котором вы пишете.


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

Я не наблюдал в gcc 7 и 8 с флагом -std=c++17 бòльших проблем, чем при использовании «стабильно» поддерживаемых стандартов. А больше всего неожиданного поведения, несовместимого со стандартом я наблюдал на msvc независимо от используемых флагов.

Я не наблюдал в gcc 7 и 8 с флагом -std=c++17 бòльших проблем, чем при использовании «стабильно» поддерживаемых стандартов.
Рад за вас. Тем не менее это не отменяет того факта, что стандарт C++17 был принят уже после релиза gcc 7.

Он не был частью какого-либо стандарта C++ на момент выхода clang 9, о проблемах в котором вы пишете.
Его статус был ровно таким же, как фичи C++17 в GCC 7. Стандарт ещё не был утверждён в момент выхода GCC 7, извините.

То, что вы не напоролись ни на какие проблемы… ну — повезло вам. Я знаю кучу народу (ну как кучу… мои знакомые, коих, понятно, гораздо меньше чем всех программистов в любой компании подобной Google/IBM/Microsoft), которые эти designated initializers используют аж с 2012го года, когда они начали поддерживаться GCC 4.7 и Clang 3.0… и вроде у них всё работает. Может у них нет объектов со ссылками внутри или они просто всегда используют их в соотвествии со стандартами C++20… не знаю.

А у нас они оказались включены по недоразумению (они не поддерживаются в режиме -std=c++17 -pedantic, но поддерживаютс в -std=c++17 без -pedantic) — и это сразу привело к проблемам.

Эффект масштаба, однако.

А больше всего неожиданного поведения, несовместимого со стандартом я наблюдал на msvc независимо от используемых флагов.
Это — немного другая история. MSVC — это весьма часто боль, но к экспериментальным фичам это отношения не имеет.
Рад за вас. Тем не менее это не отменяет того факта, что стандарт C++17 был принят уже после релиза gcc 7.

Да, перепутал версии. В проекте с gcc мы с восьмой версии -std=c++17 используем. В другом проекте на семнадцатый стандарт перешли одновременно с переходом на clang и какая это была версия я уже не смогу сходу восстановить (но это был 18-й год).


А у нас они оказались включены по недоразумению (они не поддерживаются в режиме -std=c++17 -pedantic, но поддерживаютс в -std=c++17 без -pedantic) — и это сразу привело к проблемам.

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

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

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

И говорить «если XXX поддерживается компилятором, тогда YYY».

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

Feature testing макросы сделаны для удобства разработчиков библиотек, на время переходного периода и для упрощения поддержки старых компиляторов.
Они не опциональные, они все обязательные.
Не все. intptr_t и int8_t — опциональны, FE_TONEAREST — тоже.

Да, до сих пор в стандарте старались избегать опциональных компонент (они в результате оказываются в отдельных стандартах типа ISO/IEC DTR 18037), но я не уверен, что эта политика реально полезна.
А метаклассы есть в каких-либо планах?
В планах есть — Саттер же толкает :) Когда будут — хз. Слабо верится, что успеют к С++23.
клево, развиваемся потихоньку
минутка нытья
Вот всякие рэнджи и корутины имплементировать время есть, а дыру несогласованности (void, который нельзя вернуть или создать переменную такого типа) в самом языке заделать не могут, хотя пропозал ещё с 2016 висит.
Можно же void возвращать: godbolt.org/z/k6Hpsi
А void переменные вы для чего хотите использовать?
Не знаю для чего можно использовать void-переменные, но знаю где можно применить void-ссылки.

Например RPC. У нас есть небольшая самописная приблуда, использующаяся примерно так:
auto&& [result, arg1, arg2] = RPCAdapter<int (*)(double, double)>(channel);
Понятно что обычно вместо типа функции там какой-нибудь предопределённый тип.

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

А самое весёлое бывает когда функции — это просто функция. Без аргументов и кода возврата. Тогда получается
auto&& [] = RPCAdapter<void (*)(void)>(channel);
что далеко не всем компиляторам нравится… А clang после этого ещё и начинает заявлять, что, дескать, переменная [] не используется… а как её использовать предполагается, извините?
Мы с подобными предепреждениями боремся через атрибуты godbolt.org/z/RPVwRA:
[[maybe_unused]] auto&& [] = RPCAdapter<void (*)(void)>(channel);


А чем ссылка на void поможет в этом примере? Вы хотите чтобы structured bindings работали с void?
Ссылка на void поможет тем, что не нужно будет в куче мест проверять отдельно — void там или нет.

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

Если бы ещё пустые структуры в этом языке занимали 0 байт, а не 1...

Ну по вашей ссылке выдвигается классаная теория (e1 and e2 cannot have the same address, but one of them can share withc[0] and the other with c[1]), вот только… ни один компилятор так не умеет.
Может я чё не понимаю, а разве поможет?
Ну то есть сделать
struct void_t2 {};
sizeof(void_t2) == 0;
[[no_unique_address]] не может же.
Так как ссылок на void не бывает, то приходится иметь кучу костылей в реализации
так вы как ни изворачивайтесь всё равно рано или поздно особые свойства void вылезут. Например, вот здесь void пропадает из variadic pack'а. Поэтому (имо) зачастую проще его сразу обработать отдельным юзкейсом (благо constexpr if позволяет это сделать «не отходя от кассы»), чем пытаться писать исключительно универсальный и ни разу не читаемый шаблонный код.
что далеко не всем компиляторам нравится…
ну не знаю, я вот не компилятор, но мне тоже не нравится

Нет, в вашем примере void пропадает не из variadic pack'а, а из сигнатуры функции.


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

НЛО прилетело и опубликовало эту надпись здесь

Хм, а что мешает написать вот так?


RPCAdapter<void (*)(void)>(channel);
То, что это получается ещё один случай в кодогенераторе.

Не ручками же это всё пишется.

И да, то, что f(void) — это уже «особый случай» печалит, конечно…

Я реально не вижу каких-то сложностей в данном случае:


auto&& [result, arg1, arg2] = RPCAdapter<int (*)(double, double)>(channel);
auto&& [result] = RPCAdapter<int (*)()>(channel);

У функции может быть переменное число аргументов, в том числе и нулевое. А единственный недостаток — std::tuple<> занимает не 0 байт.


Проблема же в возвращаемом значении, но я бы эту проблему решил с помощью какого-нибудь result_wrapper<T> с отдельной перегрузкой для T = void.

А единственный недостаток — std::tuple<> занимает не 0 байт.
Это тут вообще причём? RPCAdapter — это не tuple.

Проблема же в возвращаемом значении, но я бы эту проблему решил с помощью какого-нибудь result_wrapper<T> с отдельной перегрузкой для T = void.
Там хоть перегружай, хоть не перегружай — всё равно синтаксис другой.

Разрешили же делать return foo(); в функциях типа void (если foo тоже void возвращает). Вот и для других случаев того же хочется…
Это тут вообще причём? RPCAdapter — это не tuple.

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

И про то, что пустое множество параметров в variadic template допустимо и не требует к себе какого-то особого отношения.
Я где-то утверждал обратное?

Про то, что этот тип вполне себе может сидеть внутри реализации.
Это уже проблемы реализации.

Я же обсуждаю проблемы интерфейса — которые вы напрочь проигнорировали, так как в обоих ваших примерах функции что-то да возвращают и, соотвественно, проблемы пустого списка structured bindings (а по стандарту он пустым-таки быть не может) не возникает.
Есть кусок кода, который выглядит примерно так:
auto some_variable = some_function();
...
auto some_variable_2 = some_function_2(some_variable);
...
return some_variable;


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

Через if constexpr проблема достаточно легко обходится. А как компилятор потенциально должен обрабатывать вызов функции с аргументом типа void мне вообще не совсем понятно (синтаксис type function(void) — легаси из C).

А как компилятор потенциально должен обрабатывать вызов функции с аргументом типа void мне вообще не совсем понятно.

Например, так:


void foo(int);
template<typename T> int bar(T t);

// Где-то далее в коде
auto value = bar(foo(42));

Фактически передачи данных не происходит, но bar(foo(42) создаёт зависимость по данным между вызовом bar и значением, возвращаемым foo(42), поэтому компилятор при оптимизации кода не может переупорядочить инструкции так, чтобы foo вызывалось после bar.

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


Достаточно чтобы этот код просто компилировался.

Если там цепочка из достаточного количества вызовов — замаетесь вы if constexpr на каждый писать. Плюс возрастает риск допустить ошибку при копи-пасте очередного вызова внутрь if constexpr.

Ох, отличная идея — идентичный код по разным веткам «ифов» распихивать. И это если куски кода небольшие, и их немного. В противном случае вообще адища получается.
Ну и идейно на лицо костыль.
А как компилятор потенциально должен обрабатывать вызов функции с аргументом типа void мне вообще не совсем понятно (синтаксис type function(void) — легаси из C).

Не совсем понял в чём проблема. Если в несовместимости с C, то нахрен нужна эта совместимость.
Если в несовместимости с C, то нахрен нужна эта совместимость.
Уже поздно. «int (*)(void)» уже допустимо в C++ и обозначает то же, что и «int (*)()».

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

Мне в подобном сценарии хватало вот такого кода:


using ReturnType = decltype(impl(input));
constexpr bool returns_void = std::is_void_v<ReturnType>;
const auto ret_expression = [&]() {
      if constexpr (returns_void) {
        impl(input);
        return []() {};
      } else {
        return [ret_val = impl(input)]() { return ret_val; };
      }
    }();

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

Повторяется только вызов функции с неизвестным типом.

Вот его-то повторять и не хочется.

в шаблонном контексте вполне можно делать return void:
int bar(int);
void bar(float);

// ...

auto foo = [](auto... args) {
    return bar(args...);
};

foo(1);
foo(1.3f);

Проблема скорее при попытке запихать void в переменную: auto x = foo(); будет некорректен для foo(), возвращаемого (пусть даже и зависимый) void.

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

в шаблонном контексте вполне можно делать return void:
Вот как раз об этом и речь — кроме того, чтобы просто сделать return может захотеться сделать что-то ещё. В log например записать о том, что вызов успешно случился, например.

Да, есть разные способы выкрутится с помощью RAII и прочего… но сделав void обычным типом от всего этого можно отказаться. Проблема только в f(void), пришедшем из C…
Согласен, что это дыра. Но я даже не надеюсь, что её пофиксят. Советую просто забить и жить дальше. Её не пофиксят, т. к. C и C++ изначально делались без особой оглядки на теорию языков программирования. Если бы создатели C и C++ знали теорию языков программирования, они бы вместо void сделали классический unit type из теории языков программирования. Его можно было бы создавать и делать с ним всё, что угодно. Если вас парит ситуация, используйте языки, где это продумано, напр. Haskell и SML/Ocaml
Если вас парит ситуация, используйте языки, где это продумано, напр. Haskell и SML/Ocaml
Haskell и Ocaml, к сожалению, очень тяжело использовать в контекстах, где выделение и освобождение памяти может быть запрещено. На Rust я поглядываю, впрочем.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Какая foo должна быть вызвана?

Такая же как и в этом случае:


template<typename T>
void foo(T);

void foo(int);

...

int var = someVoidFun();
foo(var);
НЛО прилетело и опубликовало эту надпись здесь
antoshkka
Одна из выдающихся идей, которые нам посчастливилось представлять, — это идея Антона Жилина P2025

Поддерживаю. Часто пишу классы наподобие not_null_ptr, и подобные фичи мне очень нужны. Поэтому пришлось разобраться в теме. Дам список похожих предложений, которые связаны с написанием not_null_ptr-подобных классов: P1029, P1144, P0023.

Также советую толк от автора одного из пропозалов, разбирающий пропозалы по теме: www.youtube.com/watch?v=SGdfPextuAU. См. также quuxplusone.github.io/blog/2018/05/02/trivial-abi-101.

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