Pull to refresh

Comments 225

Такими темпами нужно будет отдельный курс в институте на 5 лет вводить "Знаток с++"

Я б в такой институт не просто пошел.. я б туда побежал)) Люблю плюсы)

Плюсы все равно уже стали слишком раздуты. Я и 1/9 всех фич не использую. Некоторые даже никогда на практике не видел.

Все, что тут было описано я не знал и не использовал)

может потому что этого ещё нет в С++? И будет только в С++23?

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

вы не можете говорить, что они не нужны вам, если вы не знаете их. 99%, что пригодилось бы что то и очень сильно

Возможно. Просто становиться все сложнее и сложнее это мониторить и понимать.

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

Я еще в НИРах использую плюсы. Поэтому мне тоже надо смотреть на это все. К тому же, может прийти заказчик НИРа и попросить реализовывать все это на 14/17 стандартах.

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

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

Мем для поднятия настроения:)

Это прекрасно! Благодарю, схораню!

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

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

В общем, в итоге я просто отказался и переписал всё без них.

Я сразу понял, что к ним я не притронусь. Слишком оверхедно для моего мозга:)

Без знания железа и как процессор все это обрабатывает, писать эффективный код на С++ невозможно. К примеру я байтоложец:) Любитель во всякую низкоуровщину, и при написании кода, всегда держу в голове, выравнивание, размер типов, влезет ли в кеш и т.д

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

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

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

Из всего С++20, полезными я вижу только концепты и гетерогенные unordered контейнеры. Из представленного в С++23, возможно только std::expected, std::print и новые контейнеры.

А как же std::stacktrace ? Классная ведь штука :)

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

Возможно, для проектов, которые стартанут году эдак а 2030-м, фича сэкономит пару-тройку часов какому-то программисту)

Ranges очень легко понять, если какое-то время поковырять операции над списками в Haskell, особенно в point-free нотации :)

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

Это было в начале 2021-го года, сейчас уже вероятно починили, но суть не в этом, а в том, что сначала я не мог понять - это я идиот иди компилятор? Ошибка была такой адовой шаблонятиной, что несмотря на весь мой опыт в метапрограммировании, я не осилил. Там был лютейший адище. Какие view, в которые вложены другие view, в которые вложены другие view.... В общем, я пошел в итоге в плюсовый чат в телеге, где собссно вроде сам Антон Полухин мне и рассказал, что это кривая реализация в GCC.

Черный пояс, по указателям. Седьмой дан по трейтам. Чемпион по UB:)

Жаль, #embed в стандарт не попадёт.

Автор пропозала жалуется, что комитет C++ слишком упрямый. Даже в C уже приняли.

В C++23 просто не успели принять, сконцентрировались на других вещах :(

Но то что приняли в C23 - очень позитивная новость. Значит скоро примут и в C++, ведь комитет старается сохранять совместимость. Ну и когда реализуют в C компиляторах, фича сама "просочится" в C++

Хм, но автор ведь пишет, что в compile time можно, разве это не то что требуется в подавляющем большинстве случаев?

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

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

В комитете есть несколько людей, которые активно следят за простотой использования языка. Например Nicolai Josuttis преподаёт C++ студентам, и часто приходит в комитет со словами "Вот этот момент студенты не понимают, и я кажется тоже! Давайте вот так переделаем" или "Тут приходится слишком много и долго объяснять, смотрите как можно упростить"

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

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

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

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


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

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

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

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

fixed

Какие-то вещи из нового стандарта студенты не понимают. 

Какие, например?

UFO just landed and posted this here

А что, студентам где-то рассказывают про std::launder?

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

А расскажите поподробнее, что именно вам пригодится?

16 битные флоаты в стандарте - то, чего реально не хватало.

Флэт контейнеры, constexpr битсеты - очень приятно.

Print, start_lifetime_as - просто приятно.

принт очень интересно выглядит с выводом на экран через свой output iterator ))

Интересно бы было примеры с юз-кейсами посмотреть. Просто на словах вам оно понятно, а как оно на практике у вас используется (можно с абстракцией) было бы интересно посмотреть.

Можно кейс применения 16-битных флоатов? Мне пока видится, если только на 16 битном процессоре. И то, у него, наверное, не будет FPU блока.

Нейросети, обработка сигналов.
Мир не заканчивается на x86/64.

Обработка фотографий с высоким динамическим диапазоном (где float32 очень много, float16 - в самый раз). HDR изображения.

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

Теоретически все современные процессоры умеют в fp16

Они умеют только делать векторное преобразование FP16 <-> FP32 с помощью AVX-команд vcvtph2ps и vcvtph2ps, но не напрямую работать с FP16.

Уву, таки векторное и нужно, по 4 числа обычно: RGB + A.

Осталось включить Unity и Unreal Engine в стандартную библиотеку.

По тому как описывали новый стандарт ранее, казалось, что выйдет самый гнусный и скучный стандарт. А тут сразу и модули, и дополнения к ренжам которых так не хватало, assume, #warning и много еще плюшек

start_lifetime_as особенно порадовал. Только недавно в procxx «воевали» на этот счет =)

Проясните, Антон, пару-тройку моментов.

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

Ещё про рэнджи. В реализации Эрика Ниблера есть такие штуки как cursor, adaptor. Их вроде как просто не успели принять в C++ 20. Я переехал на ranges v3 с ranges v2 из boost, но сейчас я не могу пользовать стандартную реализацию из-за кастом вьюс. Попали ли они в C++ 23?

std::format при всей констэкспрешности утерял, кажется, важную опцию, присущую boost::format. На аргумент в boost::format можо было напустить манипуляторы, а на std::format, насколько я понимаю - нет. Это - большой пробел.

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

на std::format, насколько я понимаю - нет

В std::format уже есть предопределённые форматтеры на ширину выведения числа, на точность, на предстваление, на заполненители и т.д.

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

Можно ли переопределить стандартную реализацию?

Да, например можно сделать

```

template <typename Range>
struct MyPrinter{ Range r; };

```

И специализировать std::formatter<MyPrinter<T>>. Тогда, если надо как-то по особомо отформатировать свой диапазон, то надо будет написать std::print("{:ваши-флаги-форматирования}", MyPrinter{range});

не могу пользовать стандартную реализацию из-за кастом вьюс

Есть способы писать свои вьюихи, но если это делать по всем правилам, получается нетривиально. Со статическим operator() это становится попроще

они конфликтуют с перегрузкой оператора (,)

Использование этого оператора в operator[] было задепрекейчено в C++20, а оператор для многомерных массивов был добавлен в C++23. Так что если есть подобные использования, то предлагается сначала мигрировать на C++20, поправить все использования, после чего уже переезжать на C++23. Решение не самое изящноеб но рабочее.

какова судьба библиотек типа boost::phoenix, boost::karma, boost::spirit

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

есть предопределённые форматтеры на ширину выведения числа, на точность, на предстваление, на заполненители и т.д

Я не стал бы кукситься на стандартые форматеры. Естественно, меня интересуют свои.

И специализировать std::formatter<MyPrinter<T>>

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

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

А deduced this не заменяет CRTP внезапно. В общем - говно, сорри. Я к вам неплохо отношусь, но истина дороже.

Извините, но нет. Стандарт C++23 хороший. На самом деле он хороший уже тем же как хорош С++14 после С++11: Огромное количество всего было доделано и детерминировано как из 20, так из 17 стандартов. Только сейчас я более или менее целиком осилила и поняла C++23 и что делать с проектами и как дальше работать.

Есть ли новости касательно std::expected? Я где-то читал что уже добавлено в 23, но возможно я ошибаюсь.

Как минимум в msvc его уже можно использовать с версии VS 2022 17.3 Preview 3

Планирует ли комитет, добавить в стандарт абстракцию над SSE1,2,3,4 и AVX512 и иже с ними? Что бы не приходилось юзать богопротивные Intrinsic:) При просмотре Intrinsic'ов глаза текут:)

Собственно с введением 128 битных чисел с SSE* можно сказать что вопрос на уровне языка закрыт, осталась только реализация в STD. C AVX пока чуть сложнее поскольку там 512 и 1024 битные регистры есть.

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

Я так понимаю execution::unsequenced_policy ещё ни один компилятор не реализовал в виде simd, кроме заглушек ? Я пробовал годболтить на основных, код ничем не отличался от стандартного расчитанного на c++threadmodel (читаем по элементу за раз, например для строк - по байту за итерацию).

std::start_lifetime_as

Почему-то ощущение что это какой-то костыль.
На счет того, что какой-нибудь:

void fun(const char* p) {
	const SomeStruct* str = reinterpret_cast<const SomeStruct*>(p)
  ...

Считается UB, мнения постоянно делятся и из-за этого родилось, то что родилось.
Все неопределенное поведение тут завязано только на отправителе или источнике указателя p, и к текущему коду никакого отношения не имеет. Как например, если этот указатель был получен из другого языка или компилятора где char 2 байта или имеет специфическое выравнивание. То есть вся проблема из-за расплывчатого описания стандарта. В этом смысле start_lifetime_as тоже мало чем поможет. А сама пометка неопределенного поведения подразумевает что угодно. Конкретно здесь несоответственное расположение данных полям структуры SomeStruct и тому что объект, который будет использоваться как SomeStruct, на самом деле им не был.

То есть можно сделать вывод, что start_lifetime_as просто затычка, чтобы люди закончили по этому поводу спорить. Но это же ни чего не меняет. По сути тот же const SomeStruct* str = (const SomeStruct*)p. из С, который по своей природе в этом плане тоже не определен.

void ReceiveData(std::span<std::byte> data_from_net) {

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

Вот только проблема строго обратная: если указатель p был получен "из другого языка или компилятора" (т.е. из внешних по отношению к компилятору штук, про которые он не знает) — то reinterpret_cast работает без всякого UB. Разумеется, если фактическое расположение и выравнивание полей соответствует ожидаемому.


А вот когда указатель p был сформирован внутри компилируемой программы — тут-то UB и вылезает на свет, угрожая покорёжить программу на этапе оптимизации. И именно здесь start_lifetime_as очень даже спасает.

А вот когда указатель p был сформирован внутри компилируемой программы — тут-то UB и вылезает на свет, угрожая покорёжить программу на этапе оптимизации.

Вы понимаете что здесь точно также можно сказать что "Разумеется, если фактическое расположение и выравнивание полей соответствует ожидаемому — то reinterpret_cast работает без всякого UB." ? Кроме того также выделение памяти под объект выполнено соответствующими средствами С++. В результате эти примеры равнозначны.

И именно здесь start_lifetime_as очень даже спасает.

По вашему описанию выглядит как static_cast.

А из примера:

void process(Stream* stream) {
   std::unique_ptr buffer = stream->read();
   if (buffer[0] == FOO) processFoo(reinterpret_cast(buffer.get())); // undefined behaviour
   else processBar(reinterpret_cast(buffer.get())); // undefined behaviour
} 

Видно что проблема здесь опять не в reinterpret_cast, а в том что buffer временный. Что из примера в статье выше это ни как не видно. И тут тоже можно обойтись исправлением без start_lifetime_as, копирования и UB.

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2590r2.pdf

Вы понимаете что здесь точно также можно сказать что "Разумеется, если фактическое расположение и выравнивание полей соответствует ожидаемому — то reinterpret_cast работает без всякого UB."

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


Видно что проблема здесь опять не в reinterpret_cast, а в том что buffer временный.

С чего бы тут была проблема из-за временного буфера?

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

Во первых, а вои и да:

Since C++20, certain functions in the C++ standard library such as malloc, bit_cast, and memcpy are defined to implicitly create objects [P0593R6]. As a result, the following code is no longer undefined behaviour:

struct X { int a, b; };
X* make_x() {
  X* p = (X*)malloc(sizeof(struct X)); // implicitly creates an object of type X
  p->a = 1;
  p->b = 2;
  return p;
}

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

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

С чего бы тут была проблема из-за временного буфера?

Немного ошибся с переводом, но тем не менее читайте описание start_lifetime_as и того что оно на самом деле делает, а не то что вы предполагаете (ссылка у меня выше и в статье). Это то, из-за чего нельзя сначала сделать malloc, а потом delete. (чего нормальному человеку в голову не придет) Собственно это и есть те условия, обходя которые можно уйти от UB.

Это вы читайте внимательнее. Некоторые функции, такие как malloc, были явно "благословлены" — но не ко всем функциям выделения памяти это относится. В частности, при замене malloc на любой другой аллокатор UB возвращается, если реализация выделения памяти доступна компилятору:


But what about non-standard memory allocation or memory mapping functions that are provided by the user? Consider, for example, a library providing a memory pool, where the storage reuse is expressed in C++ code rather than in a syscall and is visible to the compiler. The current C++20 wording does not provide a solution for this use case, and code using such storage will be undefined behaviour.

Опять вы путаете теплое с мягким и вводите в заблуждение других. Вопрос был про "Выделить массив байт" и "А вот когда указатель p был сформирован внутри компилируемой программы", где по вашему якобы помогает start_lifetime_as. И любому знающему человеку должно быть понятно в чем разница между разными парами "выделить"/"удалить" память, поскольку это разные менеджеры/системы памяти. Что собственно и делает start_lifetime_as, отключая у неявного объекта деструктор и конструктор. Поэтому ни какое ваше "кастануть его в указатель на структуру и обратиться к полю - UB" неверно ни для одного стандарта, до тех пор пока вы явно или неявно не вызвали деструктор или конструктор и не начали оперировать с таким объектом стандартными методами.

std::get<1> для агрегатов

Это magic_get для структур из коробки ? :)

Отличная инициатива, естественное и логичное развитие для языка после того, как появился structured binding !

Кажется там упущены перегрузки get для T&& и const T&& ?

И ещё хотелось бы подобный интерфейс для сумм типов, через get_if и распространить std::visit на все подобные типы, а не только вариант

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

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

В общем, я люблю и ненавижу его)

Я люто обожаю. Любовь с первого коммита:)

Давно ли был этот первый коммит? :) А то лет 10 назад я тоже был в восторге)

Около 10-ти лет назад. На плюсах писал в общей сложности около трех лет. Потом всякие C#.

Так не честно) Если С++ в роли любовницы, то понятно, что будешь его любить :)

Я с такого ракурса не думал. А ведь верно:)

Любовь с первого коммита:)

т.е. тогда уже были коммиты! :)

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

UFO just landed and posted this here

ощущение от знания С++ - как будто выучил китайский язык в мире ЯП

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

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

Писать код без него — подобно искусству каллиграфии.

В плане, что так же бесмыссленно на практике?

UB это контракт между программистом и компилятором, чтобы компилятор мог генерировать наиболее оптимальный код.
Я, такой-то такой-тович, со своей стороны, и GCC, именуемый Компилятор, с другой стороны договариваемся:
1. Я пишу такую логику, что обращение к данным по nullptr — недопустимо, а Компилятор может использовать эту информацию и не делать лишних проверок при работе с ссылками/указателями.
2. Я пишу такую логику, что нигде в программе не может быть переполнения знакового числа, и компилятор опять же может использовать это для оптимизации.
3. Я сам контролирую и отвечаю за лайфтайм объектов. Если я куда-то передал ссылку и потом удалил объект, компилятор не обязан за меня как-то чудесным образом следить за тем что ссылка на область памяти не протухла (и опять же не вставлять дополнительных проверок).

и тд и тп.

Т.е. я чисто не согласен с аналогией про каллиграфию) в остальном-то полностью разделяю ваше заявление.

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

Ай ладно, написал, перечитал, не, не, согласен про каллиграфию, не так уж и далеко :D

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

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

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

На С++ приходится выводить текст программы медленно и вдумчиво

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

нигде не ошибаясь

Как это ни странно, но ошибаться нельзя ни на Java, ни на Python, ни на SQL, ни на чем-либо еще. Ошибки другого рода, конечно же, но ошибаться все равно нельзя, иначе, как в анекдоте "такая фигня получается".

Я не пытаюсь сказать, что C++ идеальный язык, но и делать из него вселенское зло, как здесь пытаются, не нужно.

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

В принципе, как и скорость написания каллиграфиграфических символов))

Я не пытаюсь сказать, что C++ идеальный язык, но и делать из него вселенское зло, как здесь пытаются, не нужно

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

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

Из этого вовсе не обязательно проистекает вот это:

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

Поскольку при написании прикладного кода, как правило, используются:

  • ограниченное подмножество языка;

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

Грубо говоря, если у вас набор хороших готовых библиотек, закрывающих вопросы вашей предметной области на 90%, то программирование на C++ не является чем-то запредельно сложным. Более того, оно даже не требует отличного знания всего (или большей части) языка.

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

Но хорошие моменты в том, что:

  • разрабатывать C++ библиотеки нужно реже и высококвалифицированных C++ разработчиков для этого нужно еще меньше;

  • не обязательно делать что-то вроде Boost.Hana или Boost.Spirit с первого раза. Начинать можно и с более простых вещей, постепенно усложняя потроха по мере необходимости и приобретения опыта;

  • как раз развитие C++ с C++11 по C++17 показывает, что язык становится проще для этих целей (с C++20 пока дела не имел, не могу судить насколько он упрощает этот процесс, но те же концепты, при всей их сложности, все-таки лучше, чем бодание с SFINAE).

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

UFO just landed and posted this here
UFO just landed and posted this here

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

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

Если планируете собирать собственный модуль, то придется написать немного cmake текста для создания таргета и его установки. Со временем писать придётся меньше, так как добавят нужный функционал https://gitlab.kitware.com/cmake/cmake/-/issues/18355

`std::start_lifetime_as` вводит новые концепции?
std::start_lifetime_as actually creates a new object and starts its
lifetime (even if no code runs). On the other hand, std::launder never creates a new object, but
can only be used to obtain a pointer to an object that already exists at the given memory location

Что порождает несколько вопросов:
1. В примерах std::start_lifetime_as используется только для POD. Разве POD — не просто набор байт нужного размера с выравниванием?
2. Что за новая концепция «creates a new object»? И в чём её особенности для POD?
3. Если мы создали новый объект, то как его корректно уничтожить? И как не задеть уничтожение объекта, который тоже там находится (char[])?
4. Надо ли проводить такие же манипуляции для превращения POD/POD's fields в байты (например, при вызове memcpy() при подготовке к отправке по сети)?

Это довольно древняя концепция, и start_lifetime_as не вводит её, а затыкает дыру которая мешала писать оптимальный и соответствующий стандарту код.


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


Разумеется, реальная память линейная (по крайней мере, на популярных архитектурах). Но это не означает что любые операции указателями автоматически становятся до-определены! Поскольку с точки зрения стандарта при UB допустимо абсолютно любое поведение, компиляторы активно этим пользуются для оптимизации.


В примерах std::start_lifetime_as используется только для POD. Разве POD — не просто набор байт нужного размера с выравниванием?

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


Если мы создали новый объект, то как его корректно уничтожить? И как не задеть уничтожение объекта, который тоже там находится (char[])?

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


Надо ли проводить такие же манипуляции для превращения POD/POD's fields в байты (например, при вызове memcpy() при подготовке к отправке по сети)?

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

Стандарт явно разрешает работать с любым объектом как с набором байт

(char[], unsigned char[], signed char[]) или любой фундаментальный тип?
Просто иначе странная ситуация: offsetof() существует, фундаментальный объект существует, а их обоих в сборе — нет.
Upd: вот это имелось в виду?
An operation that begins the lifetime of an array of char, unsigned char, or std::byte implicitly creates objects within the region of storage occupied by the array.


проблемы были лишь при попытке работы с набором байт как с объектом.

А какие проблемы? Компилятор может соптимизировать чтение из char* до и после изменения POD по тому же адресу?
А главное — зачем вводить новую функцию, а не «просто» перенести этот функционал на type cast "(T*)"?
(char[], unsigned char[], signed char[]) или любой фундаментальный тип?
Просто иначе странная ситуация: offsetof() существует, фундаментальный объект существует, а их обоих в сборе — нет.

Честно говоря, я не понял что вы сейчас спросили.


Upd: вот это имелось в виду? An operation that begins the lifetime of an array of char, unsigned char, or std::byte implicitly creates objects within the region of storage occupied by the array.

Нет, это отдельный костыль, созданный чтобы PODы можно было создавать при помощи malloc, иначе совсем несовместимо с Си получалось.


А какие проблемы? Компилятор может соптимизировать чтение из char* до и после изменения POD по тому же адресу?

Именно так. Или может выкинуть ветку условного оператора увидев такое чтение. Или может прочитать нули вместо данных.


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


А главное — зачем вводить новую функцию, а не «просто» перенести этот функционал на type cast "(T*)"?

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

Честно говоря, я не понял что вы сейчас спросили.

Не очень понятно, где проходит граница UB.
struct header_t { uint16_t type; uint8_t mask; };
uint8_t buf[512];

auto r1 = ((header_t*)buf)->mask; // UB
auto r2 = (new(buf) header_t)->mask; // No UB
auto r3 = *(buf + offsetof(header_t, mask)); // No UB?
auto r4 = *((uint16_t*)(buf + offsetof(header_t, type))); // UB?

Я заскриню, хочу создать мем. Ничего необычного, стандартный код на С++:)

r1 — UB
r2 — нет никакого UB, объект был создан оператором new
r3 — никакого UB, тут разыменование корректного указателя
r4 — UB, на самом деле этот пример такой же как и первый

На счет r2 не все так однозначно, для buf следовало бы использовать alignas(header_t). ИМХО.

Спасибо за пояснения.
Краткий вывод: можно использовать type cast только при:
1. приведении к базовому/наследному классу (они существуют);
2. приведении из void*, например, в pthread_create()'s start_routine (объект существует).

Во всех остальных случаях надо использовать placement new/std::start_lifetime_as.

Когда компиляторы начнут полностью поддерживать с++20 ?!

В C++23 было много багфиксов в C++20. Сейчас вроде пыль улеглась, так что стандартные библиотеки начнут активнее новый функционал реализоввывать. Ядерная же часть языка C++20 подтормаживает, и не понятно сколько будет тормозить: корутины и модули оказались крайне сложными для реализации.

Планирует коммитет запилить нормальные, стандартные, быстрые regexp'ы? Типа таких но в стандарте https://github.com/hanickadot/compile-time-regular-expressions

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

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

Лично мне меньше всего хочется видеть в стандарте библиотеки для работы с HTTP, XML, JSON, YAML и чем-то подобным.

Такое должно жить в сторонних библиотеках.

А вот какой-то штатный пакетный менеджер и вменяемая штатная система сборки необходимы (хотя я и не представляю себе как такое в сложившихся реалиях возможно). Тогда проблем с тем, чтобы взять для работы с RESTful какие-то сторонние либы для HTTP, JWT, JSON, не будет вообще. И незачем все это Г, которое рано или поздно устареет, тащить в стандарт.

ЗЫ. Просто в качестве примера: в середине нулевых XML был "нашим всем", пихали его во все дырки без устали и проблесков здравого смысла. На его же фоне еще и SOAP расцветал буйным цветом, хотя где-то рядом еще и XML-RPC трепыхался. Ну и где это все спустя 15-17 лет? Так что еще лет через 15 мы можем тоже самое увидеть и по отношению к JSON, HTTP и прочим мейнстримовым сейчас технологиям.

Отчасти согласен, хотя есть контр пример. Ranges в стандарте. Столько потратили времени на их проработку, исправление, а их юзает 2.5f человека. Header занимает 1.5 мб.

Если библиотеку не юзают. Всегда можно сделать деприкатед и исключить. http и json это базовые библиотеки для разработки web'a я бы еще добавил протокол fcgi.

хотя есть контр пример. Ranges в стандарте

Сколько они в стандарте? Сколько компиляторов сейчас полностью поддерживают C++20? Сколько проектов, особенно с длительной историей, успело переключиться на C++20? Сколько курсов в ВУЗах (пусть даже в СНГ) успело включить ranges в программу?

Рано судить о том, сколько людей юзают ranges. Вы бы еще сегодня сделали далеко идущие выводы о востребованности короутин из C++20.

Всегда можно сделать деприкатед и исключить.

Да вы что? Самому приходилось нести ответственность за проект с n-летней историей, который пришлось переводить с C++98 на C++11? Например, замена auto_ptr на unique_ptr хоть и делалась тривиально, но это была работа, которую нужно было кому-то работать. Которая становится еще более веселой, если приходится какое-то время держать ветку и для C++98, и для C++11.

http и json это базовые библиотеки для разработки web'a

Этих библиотек (для http в частности) сейчас можно сходу пяток назвать, если не десяток. Какая из них должна войти в stdlib и почему? Какой интерес затем делать что-то лучше, если в стандарт войдет какой-нибудь Boost.Beast и куча народу со временем начнет кричать "а нам лучшего и не нужно".

Мало критики было на std::regex и std::unordered_map, наверное. Хотя пора было бы уже на ошибках учиться.

Впрочем, зачем http и json ограничиваться. Давайте еще и GUI в stdlib засунем. Не, у а чё? Разработка десктопа на C++ все еще одна из тех ниш, где C++ пока что нужен и держит конкуренцию. А еще криптографию в stdlib нужно запихнуть, а то зачем выбирать OpenSSL, botan, Crypto++ или еще что.

И ведь этот список можно продолжать. Почему бы не иметь в стандарте аналог Threading Building Block. Или tensorflow. Или нужное вписать.

Зачем останавливаться, если потом можно сделать деприкатед и исключить?

Сколько они в стандарте? Сколько компиляторов сейчас полностью поддерживают C++20? Сколько проектов, особенно с длительной историей, успело переключиться на C++20? Сколько курсов в ВУЗах (пусть даже в СНГ) успело включить ranges в программу?

Рано судить о том, сколько людей юзают ranges. Вы бы еще сегодня сделали далеко идущие выводы о востребованности короутин из C++20.

Статья до и после Ranges

https://habr.com/ru/company/otus/blog/456452/

По мне лучше бы в С++11 модули добавили, уже с десять лет бы программисты их юзали. Я говорю о добавлении практичных возможностей, для работы с вебом, парсингом актуальных форматов. Что тагого принесли ranges?

Да вы что? Самому приходилось нести ответственность за проект с n-летней историей, который пришлось переводить с C++98 на C++11? Например, замена auto_ptr на unique_ptr хоть и делалась тривиально, но это была работа, которую нужно было кому-то работать. Которая становится еще более веселой, если приходится какое-то время держать ветку и для C++98, и для C++11.

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

Этих библиотек (для http в частности) сейчас можно сходу пяток назвать, если не десяток. Какая из них должна войти в stdlib и почему? Какой интерес затем делать что-то лучше, если в стандарт войдет какой-нибудь Boost.Beast и куча народу со временем начнет кричать "а нам лучшего и не нужно".

Мало критики было на std::regex и std::unordered_map, наверное. Хотя пора было бы уже на ошибках учиться.

Впрочем, зачем http и json ограничиваться. Давайте еще и GUI в stdlib засунем. Не, у а чё? Разработка десктопа на C++ все еще одна из тех ниш, где C++ пока что нужен и держит конкуренцию. А еще криптографию в stdlib нужно запихнуть, а то зачем выбирать OpenSSL, botan, Crypto++ или еще что.

И ведь этот список можно продолжать. Почему бы не иметь в стандарте аналог Threading Building Block. Или tensorflow. Или нужное вписать.

Зачем останавливаться, если потом можно сделать деприкатед и исключить?

Я говорю о базовых библиотеках. GUI возможно и стоит внести, в java есть несколько штук и нормально живут и прогают. Хочу java без java:) Стандарт С++ он сам по себе мал. Если даже добавить 10 библиотек он вырастет в два раза. Если сравнивать со стандартными библиотеками python или java, это даже не рядом.

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

Статья до и после Ranges

Как она отвечает на заданные мной вопросы?

Если вам кажется, что ranges ничего не вносят и бесполезны, то могу вам привести пример из давней (для вас особенно) истории: STL и <algorithm> в частности. Стандартизировали в 1998-ом, исходники RogueWave STL ходили по Интернету года с 1996-го, наверное. Сколько лет потом люди вместо std::find/find_if или std::accumulate вручную for-ы выписывали? Да лет 10, если не 15, минимум. Некоторые и до сих пор выписывают.

Ranges -- это такая штука, последствия которой нужно будет оценивать лет через 10, не раньше.

По мне лучше бы в С++11 модули добавили

Если бы у бабушки было известно что, то это был бы дедушка. История не знает "если бы". Не приняли в C++11 модули (я даже не помню, была ли тогда речь о модулях вообще). Концепты, которые должны были стать киллер-фичей C++11 и много обсуждались года с 2007-го, в язык попали только в C++20.

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

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

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

Вы не поверите, но в мире C++ далеко не всем нужна работа с Web-ом. А кому она нужна, те уже закрыли свои нужды как с помощью полудюжины живых OpenSource проектов, так и с помощью своих внутренних, закрытых разработок. Причем, если где-то делают серьезный Web на C++, то там и требования специфические, там штатная реализация Web-сервера из stdlib вряд ли будет востребована (по аналогии с тем, как сейчас востребован std::regex там, где важна скорость).

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

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

Если даже добавить 10 библиотек он вырастет в два раза.

А теперь главный вопрос: кто будет оплачивать сей банкет?

Сколько лет потом люди вместо std::find/find_if или std::accumulate вручную for-ы выписывали?

А интересно, действительно ли нужны все эти тривиальные стандартные алгоритмы, которые не делают какую-нибудь нетривиальную вещь типа бинарного поиска? Может, и правда иногда проще написать for руками? Тоесть да, понятно, что на пиар этих функций ушло довольно много ресурсов, но как бы повернулась история, если бы этого всего не было бы? (Причем я не только в рамках C++ это рассматриваю)

Может, и правда иногда проще написать for руками?

Тривиальные алгоритмы, как минимум, абстрагируют вас от конкретного типа контейнера. Сегодня у вас какой-нибудь `MyType items[10]`, завтра `std::vector<MyType> items`, послезавтра `std::list<MyType> items`, а через месяц `std::array<MyType, 1024> items`. И вам не нужно будет менять код, написанный в стиле `find(begin(items), end(items), value)`.

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

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

Ну, контейнеры меняются не так часто,

It depends.

или плюнуть и написать свой for

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

Возможно. Просто мысль была такая (только сейчас ее смог сформулировать).

Вот возьмем управление памятью: сборщик мусора в большинстве языков, деструкторы (то бишь, unique_ptr, shared_ptr и т.п.) в C++. Вродебы очевидно, что если все эти подходы взять и выбросить и заставить программистов писать в стиле C, с ручным управлением памятью, то будет сильно-сильно больно. Тоесть все эти идеи и правда дают какую-то пользу. А вот если также взять, и выбросить все stl-и, range, linq и т.п., что изменится? Ну да, первую неделю программисты будут хмыкать, не найдя любимой функции, а потом, никто и не вспомнит об этом, или что-то да изменится?

А вот если также взять, и выбросить все stl-и, range, linq и т.п., что изменится?

Одна из задач, которая решается стандартной библиотекой, -- это организация "общего словаря", который позволяет общаться кускам кода от разных разработчиков. Т.е., если либа A принимает на вход std::string и возвращает std::string, то ее легко подружить с либой B, которая принимает на вход std::string и возвращает std::string.

Если у нас такого "общего словаря" нет (либо он низведен до int, float, double и char*), то:

  • каждая либа (или каждый проект) будет переизобретать собственную базовую библиотеку (грубо говоря у всех свой собственный string);

  • при необходимости задействовать либы A и B в одном проекте придется преобразовывать string из либы A в string из либы B. И наоборот.

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

Ну вы это про типы данных. А я именно про тривиальные алгоритмы

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

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

А изначально это был серьезный прорыв. Переход на другой уровень абстракции. Который требует некоторого сдвига по фазе в голове у разработчика (не такой серьезный, как при переходе от процедурного программирования к ООП или от ООП к ФП, но все-таки).

Были бы в STL только стандартные контейнеры, людям бы пришлось бы писать свои аналоги функций из <algorithm>. Хорошего в этом было бы не много.

Были бы в STL только стандартные контейнеры, людям бы пришлось бы писать свои аналоги функций из <algorithm>. Хорошего в этом было бы не много.

Было бы намного больше кода. Контейнеры * алгоритмы. Сейчас с концепцией итераторов, контейнеры + алгоритмы.

Я думаю, и сейчас нету никакого разнообразия в виде контейнеры * алгоритмы. Зачем например нужен std::find для unordered map? Почему std::min_element не может определить, что перед нами например set и выполнить поиск за логарифм? И т.д.

Логика в том, что каждый тип данных решает свои задачи, и не факт, что такое комбинаторное произведение возможности в принципе нужно.

Забыл добавить про обобщенное программирование. Вот если мы пишем шаблонный код, в который кто-то передает контейнер (мы не знаем что именно это за контейнер: list, vector или deque) и нам нужно что-то найти в этом контейнере, то std::find нам поможет. А вот без std::find в шаблонном коде как делать простой поиск? Писать вручную for на обобщенных итераторах? Так ведь получим тот же самый std::find. Только вот std::find написан один раз, а не будь его, то всем пришлось бы свой аналог делать.

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

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

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

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

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

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

Так ведь индексация не для всех типов будет эффективной.

Так ведь индексация не для всех типов будет эффективной.

Я понимаю, естественно. Могу лишь повторить вашу фразу, что "обобщенное программирование - не серебрянная пуля". Без алгоритмов stl она была бы еще чуть-чуть менее серебрянной

Был бы с самого начала доступен range for — можно было бы и его писать каждый раз. Но range for решили не вводить, всякие std::find показались удобнее...

Я сомневаюсь, что в начале 1990-х обсуждался range for. Тогда как std::find в те времена можно было сделать не меняя языка. И сделали, что характерно, в виде отдельной либы. Это потом уже STL стал расшифровываться как Standard Template Library.

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

Одна из задач, которая решается стандартной библиотекой, -- это организация "общего словаря", который позволяет общаться кускам кода от разных разработчиков. Т.е., если либа A принимает на вход std::string и возвращает std::string, то ее легко подружить с либой B, которая принимает на вход std::string и возвращает std::string.

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

Работает только

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

Во-вторых, в чем смысл вашего комментария? В том, что все еще есть случаи, когда наружу нужно выставлять plain C interface? В том, что "общий словарь" -- это не то, за что стоит сказать спасибо стандартной библиотеке?

Вот честно не понял к чему вы все это.

Во-вторых, в чем смысл вашего комментария? В том, что все еще есть случаи, когда наружу нужно выставлять plain C interface?

Совершенно верно. Что есть проблемы при связывании с разными компиляторами. Но это и преимущество, так как дает поле маневра.

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

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

Задумываются разработчики библиотек. Еще один пример в копилку. Это идиома pimpl, указатель на класс для сокрытия платформозависимого кода. Явный пример Qt и sfml. Главный минус, это лишнее обращение к памяти по указателю, на ровном месте. Плюс бинарная совместимость. Вроде как модули поправили этот недостаток.

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

Задумываются разработчики библиотек.

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

Ну и я сильно удивлюсь, если хотя бы треть С++ных библиотек, доступных через vcpkg и/или conan, как-то учитывают этот фактор.

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

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

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

Если эти вековые болячки не относятся к стандарту языка

А они относятся?

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

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

Пример iostream, виртуальные методы и т.д Они никогда не будут быстрыми, так как апи диктует определенную функциональность. Я знаю о fastio.

А они относятся?

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

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

Ну и я сильно удивлюсь, если хотя бы треть С++ных библиотек, доступных через vcpkg и/или conan, как-то учитывают этот фактор.

Значит этот недостаток больше надуман.

из за несовершенства концепции сокрытия данных.

Я не уверен, что концепция сокрытия данных работает в языке, где все объекты это value-types и их можно размещать на стеке. Т.е. когда мы компилируем пользовательский код, мы уже должны знать и размер объекта, и выравнивание для него.

Впрочем, модули в C++20 завезли, теперь осталось дождаться первых реальных результатов их использования.

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

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

UFO just landed and posted this here

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

Пример.

https://habr.com/ru/company/sportmaster_lab/blog/675880/

UFO just landed and posted this here

Валидация не бесплатна. Затраты будут на любом языке. На С++ затраты минимальны. Плюсы берут для скорости со знанием дела. Сегфолты несколько преувеличены. Если нет цели получить максимальную производительность, то и не имеет смысл юзать плюсы. Берём питон, С#, java и кодим. Но если цель производительность, забыть или не успеть оптимизировать код это нонсен.

GC это ещё один уровень тормозов, да он может в среднем быть быстрым. Но затупы будут, Возможно в будущем появится некий адаптивный GC, и по мере работы сможет выбирать стратегии работы. Но сейчас это лишний груз. В серьезном проекте не будет явного new, будут свои аллокаторы, менеджеры объектов и т.д И это принесет больше перформанса и получится сэкономить время разработки , чем бороться с GC.

UFO just landed and posted this here

Нет, если вы какие-то вещи смогли доказать статически.

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

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

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

Из серьезной работы на плюсах пилил веб сервис онлайн мониторинга кое какого оборудования. Fcgi + PostgreSQL. Самописный балансировщик, шаблонизатор, парсер запросов, подобие редиса но на минималках, но работало на уровне сервиса в одном адресном пространстве и т.д Больше проблем было именно реализовать и заставить работать вместе. Я не штатный разработчик на плюсах, писал на нем около трёх лет. Мог просто не успеть заценить всю красоту сегфолтов и других приколюх:)

Или раст. Или, прости ЛММ, хаскель — я на нём регулярно получаю производительность уровня плюсов.

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

UFO just landed and posted this here

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

В разработке настолько серьезного ПО опыта нет. Да алгоритмы рулят. Как пример когда пилил шаблонизатор html, для поиска имён переменных и файлов юзал простой поиск по файлу. Работало быстро, но не достаточно. С количеством файлов и бинд параметров росло время. Я сделал предкомпайл файлов. При загрузке html шаблона, строил таблицы переменных. И при поиске просто лез в хеш таблицу, и зная в каком кешированном файле и на какой позиции начинается и заканчивается имя переменной формировал результат. Скорость возросла на порядок. И ещё большой плюс в том, что плюсы мне не ставили палки в колеса неэффективной реализацией стандартной библиотеки. Как пример я пишу на C#, в нем виртуально чуть менее чем все, классы это обращение к куче, вызов метода это лишнее обращение к памяти, около 200 тактов. И когда это миллионы вызовов виртуальных методов, начинает тормозить. В С++, все шаблонизированно поэтому такой проблемы нет. Есть проблемный iostream, но я его проблемы знаю, потому ограниченно использую.

UFO just landed and posted this here

Вы уверены, что задали вопрос по адресу?

Кстати, полностью поддерживаю сарказм. Вроде как сначала имели конвертеры в стандарте, потом в С++17 их вдруг задепрекейтили, без объяснений. Ничего нового не завезли. С u8"" строками вообще не понятно. Допустим до С++20 в качестве контейнера для UTF-8 я могу использовать std::string и u8"" строки, которые сейчас преобразуются к char. Интересно, как предполагается работать с UTF-8 в дальнейшем, когда у нас появляется тип char8_t, что с обратной совместимостью к тоннам уже существующих API?
Пока приходится пользоваться своим велосипедом: sutfcpplib, но почему-то ничего готового не появляется в стандарте, хотя бы базовые вещи :(?
2 дня назад — первый релиз? поздравляю)

Пойду пару советов в issue закину
2 дня назад — первый релиз? поздравляю)
:) ну да, действительно, выглядит как-будто либу в ответ на комментарий настрочил. На самом деле она в продакшене используется уже пару лет, а вот протестить под несколько платформ, оформить и т.д. — руки могут год не доходить.
Пойду пару советов в issue закину
Спасибо, осмыслю тогда и отвечу, что бы не плодить офтопик тут.

Я знаю о существовании гитхаба, бери и юзай.

Если бы все проекты на C и C++ были снабжены хотя бы нормальными проектными файлами для CMake (путь это и Г, но это хоть какой-то де-факто стандарт в нашем мире), то этот подход бы более-менее работал.

Но это не так.

Временами нужно потратить туеву хучу времени на то, чтобы встроить чужую библиотеку в свой проект.

vcpkg/conan постепенно меняют ситуацию в лучшую сторону. Но, насколько я могу судить, до удобства других языков (Java с Maven или Rust с cargo) тут еще как до Пекина раком :(

CMake распространенное решение. Я вообще с подозрением смотрю на библиотеки общего использования без файлика CMakeLists.txt

Возможно со временем добавят и пакетный менеджер. В С++43:)

Я вообще с подозрением смотрю на библиотеки общего использования без файлика CMakeLists.txt

Вот еще бы разработчики FFMPEG ваше мнение разделяли бы...

Насколько я понимаю, разработчики ffmpeg'а старорежимники староверы. CMake всего 20 лет, вдруг не выстрелит.:)

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

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

>> Да вы что? Самому приходилось нести ответственность за проект с n-летней историей, который пришлось переводить с C++98 на C++11?

Обнимаю, если позволите. Я так "полтора раза" переписывала FlylinkDC++ чтобы ядро самой программы обновить и чтобы с C++03, местами со странного кода ещё из 90х до С++98 на C++11 переписать... Первый раз: три месяца проект просто не компилировался из-за несовместимости API всего со всем. На 4 месяц оно скомпилировалось и даже стало вполне прилично запускаться и работать, несмотря на то что часть механизмов пришлось написать с нуля сохраняя функциональность и нового ядра и основной программы и не проверяя до этого момента в runtime вообще ничего. Переход на новый стандарт C++ был не очень больной из-за вменяемой обратной совместимости и частично происходил параллельно, в т. ч. ускорив хотя бы частичную сборку каких-то отдельных файлов и соответственно возможности запускать на них статический анализ. Очень много функциональности пришлось нарезать ifdef-ами, чтобы хоть как-то ускорить процесс и уже отдать альфу и другим разработчикам тоже. В общем это правда суровое очень занятие.

Так вот, написала я это, чтобы продолжить вашу мысль и: если бы в C++ стандарте C++03 были бы сокеты, то боли у меня было бы немного меньше. Позже сокеты переписали на boost. Так что насчёт std::gui или human interface в общем смысле я не соглашусь. На будущее я предполагаю, что примитивы некоторые всё-таки попадут в стандарт, но это будет явно не раньше чем executers и reflection. Т. е., возможно в C++30 или даже позже.

вы реально не видите разницы между фундаментальным и неустаревающим ranges и stl алгоритмами и json? Может вам не на с++ писать надо?

Разницу вижу. Позвольте самому решать, на чем мне писать.

Ханна работала над продвижением CTRE в стандарт, сейчас кажется отвлеклась на статическую рефлексию.

Расширения в стандарт уже есть, они называются TS. Например из недавнего - Transactional Memory TS

Какая судьба чистых ( pure ) функций ? идёт ли работа в этом направлении ?

Нашел картинку в тему. Думаю с выходом нового стандарта, у многих программистов С++ проскальзывает такая мысль:)

скорее:
«Любой язык без той херни которая мне не нужна:»
(клочок туалетной бумаги)

Лично меня как С++ разраба факт появления новых фич не беспокоит. Меня больше беспокоят случаи торопливого добавления фич. Которые по итогу оказываются непродуманными. std::filesystem, модули, да много таких примеров.

Что не так с std::filesystem? Я когда его начал использовать думал, чет сложно. Потом увидел красоту и удобство. Универсальные пути, поддержка utf, удобство поиска и итерирования по каталогам и файлам.

ну его там почти «as is» из буста взяли.
по итогу в стандарт попала версия где все функции продублированы — с исключениями и кодами возврата. (комитет на тот момент не определился, какая стратегия обработка ошибок целевая). Но этого мало, потом пришлось с некоторых версий с кодом возврата снимать noexcept т.к. они таки могут кинуть исключения. сурпрайз (уверен 99% юзеров std::filesystem этого не знают). Ах да, часть функций чисто в одном варианте, т.к. оператор ++ например не сделать с кодом возврата.
Понятно, что на базовом уровне все круто, но если ты делаешь решение которое должны юзать в миллиардах строк кода по всему миру, там немножко выше планка качества) если чо, все выше сказанное это мое высасывание из пальца, это про что сами члены комитета говорят.

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

Для оператора есть специальная функция инкремента принимающая error code

Для оператора есть специальная функция инкремента принимающая error code

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

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

std::print выглядит как шутка. Ну то-есть комитет и до этого вводил то, что уже было, ну это было оправдана полностью заменой с-стиля на с++-стиль, ну std::print заменяет спп функцию . В C++ все больше и больше возможностей посрать 2000разными способами. Я Понимаю мотивацию создание print, ну блин...

Если вы имеете ввиду printf, то она ну очень-очень устарела. Нет поддержки std::string, да и указывать тип данных в формате - это излишне, так как плюсы их могут вывести сами. До std::print и std::format, если нужна функция форматирования, и не хотелось использовать потоки вывода, обычно переходили на плюсы

так я не отрицаю причину print'a, ну c++ выглядит больше всего как посрать 200способами

Это называется "высокий скрытый порог входа" и да — это проблема языка и над ней работают.

Обсуждается ли возможность добавления restrict квалификатора или каких-нибудь аналогов?

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

int val;

void foo(unsigned char* p1, unsigned char* p2, int& i1, int& i2)
{
    std::assume_no_alias(p1, p2, i1, i2, val);
    
    i2 = 1;
    p1[10] = p2[0];
    *p1 = i1;
    val = p2[10];
    i2 = 2;
}

В приведённом примере, после вызова функции std::assume_no_alias(p1, p2, i1, i2, val) компилятор мог бы быть уверен, что объекты i2, p1[10], p2[0], *p1, i1, val, p2[10] расположены в попарно неперекрывающихся областях памяти.

Данная функция особенно полезна была бы для указателей char*, для которых из-за опасности алиасинга компилятор вынужден генерировать менее производительный код. Пример: https://godbolt.org/z/PqWj61ePz

В теории, в стандарте уже есть std::execution::unseq. И он должен решить эту проблему для строк и контейнеров, но я не нашел компиляторов которые его бы реализовали.

Хм, поясните, пожалуйста. Насколько я вижу по примеру разница только в inline функции memset, я правильно понимаю? Если так, то мне непонятно в чём суть претензии к стандарту и модификаторам языка поскольку это больше похоже на особенности реализации GCC указанной версии и того, что его разработчики ещё не всё легаси у себя подчистили.
Вот пара ссылок: в т. ч. с отсылками к уже древним исследованиям и текущую ситуацию где уже у обоих компиляторов этой опции просто нет.

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

А всё всё ещё жду момента, когда строки в std станут C# подобными

А они никогда не станут. Обратная совместимость же.


Кстати, а что вы вкладываете в понятие "C#-подобия"?

Что нужно чтобы их прорвало добавить property в C++ ?

Напишите proposal с хорошей мотивацией. Пока что все попытки разбивались об отсутствие мотивации. Мотивация должна показывать, что с новой фичёй добавляются недоступные до этого возможности, код становится проще для понимания и компактнее. Так же надо продумать, что происходит при передаче property в шаблонную функцию foo(auto&), и как вообще property взаимодействуют с ADL.

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

А еще хочется, чтоб их прорвало стандартизировать типы с фиксированным размером. Например int64_t зависит от компилятора:

  • GCC 32, Clang 32, VS 32, VS 64 - long long

  • GCC 64, Clang 64 - long

Следущий код успешно скомпилируют VS 32, VS 64 и GCC 32, но не скомпилирует GCC 64:

#include <cstdint>

struct S
{
    S(int i32) {}
    S(long long i64) {}
};

int main()
{
    int64_t x;
    S s(x);
}

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

Э-э-э, а зачем так писать?


Если конструктор S ожидает тип переменного размера, то такой и надо ему передавать же...

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

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

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

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


А если хочется строгости, то есть Rust.

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

Смысл то моей претензии в том, что вот на бумаге полезнейший тип с фиксированным размером, но на практике его использовать невозможно, потому что он непредсказуемым образом на разных компиляторах конфликтует с разным кодом. Этот тип совместим с каким-то произвольным типом, но при этом другие типы, которые имеют одинаковый размер на данном компиляторе (long и long long на GCC64) вдруг являются несовместимыми. Сделайте тогда уж типы с фиксированным размером отдельными типами вроде std::byte

но при этом другие типы, которые имеют одинаковый размер на данном компиляторе (long и long long на GCC64) вдруг являются несовместимыми

А на VS та же хрень с int и long.

Внезапно и на GCC32 тоже с int и long

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

А теперь представьте себе, что вы компилируете этот код на системе, где и тип int, и тип long, и тип long long — это всё 64-битные целые. Вообще веселуха будет.

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

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

Так я и говорю, что хорошо бы стандартизировать такое

Почему? Это поломает кучу кода.

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

И вообще для меня странен сам подход тянуть совместимость с несуществующими платформами.

Также считаю, что должно быть в исходниках что-то вроде #pragma CPP20 означающая, что вот это вот приложение/библиотека/единица трансляции должна компилироваться компилятором в режиме C++20. Тогда можно ломать обратную совместимость хоть в каждой версии.

И вообще для меня странен сам подход тянуть совместимость с несуществующими платформами.

Вы ограничиваете мир только x86/x64, что лишь показывает ваше невежество.
Этих несуществующих платформ довольно много — всего-навсего embedded различного сорта, DSP там всякие.


Также считаю, что должно быть в исходниках что-то вроде #pragma CPP20 означающая, что вот это вот приложение/библиотека/единица трансляции должна компилироваться компилятором в режиме C++20.

И что будете делать с инклюдами?

Когда Вы используете extern "C" void f(int); или даже вставки ассемблера в C++ коде Вас почему-то не беспокоят инклюды (что показывает Ваше невежество).

Вы не ответили на мой вопрос. Что будете делать с инклюдами? Цепочка файлов, и у каждого своя pragma. Как правила будут применяться?

Очень просто. Если в заголовке есть #pragma CPP20, то его содержимое считается как бы обрамлённым extern "CPP20" { ... } по аналогии с extern "C" { ... }. Каждое объявление в итоге получается внутри какого-то блока, который обрабатывается по правилам какой-то версии стандарта. Ну и они могут быть вложенными.

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

Вот только есть незадача: файлы в C/C++ не являются самостоятельными единицами трансляции. Это предложение, фактически, то же самое, что сделать file-scoped using namespace.

Что непонятного во фразе сделать по аналогии с extern "C" { ... }?

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

Хорошо, допустим сделали по аналогии:


some_directive
{
    // code
}

Это ещё один вложенности. Неприятно, ну да ладно. Теперь напишите, что конкретно должно происходить внутри этого блока.


С моей точки зрения, возможны такие варианты:


  1. Запрет неявных преобразований к типам меньшей размерности, как это сделано, например, в C#. Но это не решит проблему преобразования к типам больших размерностей: если у нас две функции, которые принимают соответственно long и long long, а у нас int, тогда компилятор не сообразит, что делать.


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



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

Ага, я такой :)


Вы просто упорно не хотите понять, что то, что вы хотите поменять, является краеугольным камнем C++.

Раз вы переходите на личные оскорбления, то попрошу вас предоставить пруфы на "всего-навсего embedded различного сорта, DSP там всякие" не из прошлого века, где "int, и тип long, и тип long long — это всё 64-битные целые"

Это Ваш ответ на мой вопрос?

Да. Экзотики в виде 8-байтного int уже давно нет. Но вот int с размерами либо 2 байта, либо 4 байта встречается сплошь и рядом. И потому int32_t может быть алиасом и для int, и для long. И вам придётся с этим мириться.

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

В C23 уже наложили ограничения на размер типов, C++ на очереди очевидно https://open-std.org/JTC1/SC22/WG14/www/docs/n3054.pdf

The rank of long long int shall be greater than the rank of long int, which shall be greater
than the rank of int, which shall be greater than the rank of short int, which shall be greater
than the rank of signed char.

Если что, integer conversion rank — это не разрядность, а просто индекс в ряду "signed char, short, int, long, long long"

2-байтный int - это ладно, вот из того, с чем я лично сталкивался не так давно:

http://downloads.ti.com/docs/esd/SPRU514I/Content/SPRU514I_HTML/tms320c28x_c_c_language_implementation.html#STDZ055484

16-битный char, он же short, он же int. "Совместимость с несуществующими платформами", ага. Узок круг[озор] у некоторых комментаторов, страшно далеки они от народа.

И что интересно, sizeof(int) == 1 в данном случае.

Для твоего старья есть старый компилятор. Тебе C++23 нужен на этом?

Sign up to leave a comment.