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

Возможно, самым практичным был бы вариант, когда C++ окончательно уходит на обочину и повторяет судьбу COBOL-а – где-то как-то тихо и мирно живет себе за счет многолетнего легаси, которое приносит деньги и которое никто не решается переписать на чем-то модном и молодежном. А новые стандарты не столько пытаются модернизировать язык, сколько поддерживают его на плаву.

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

OK, допустим, что цель в том, чтобы C++ счастливо и успешно прожил еще 40 лет, постоянно развиваясь и адаптируясь к новым условиям. Что для этого нужно сделать сейчас? Важный дисклеймер: на мой сугубо субъективный взгляд.

Изменение метода наполнения стандарта

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

В итоге C++ разработчики имеют дело даже не с двумя, а с тремя разновидностями C++:

  • то, что описано в стандарте, но еще нигде и никак недоступно (а когда будет доступно – хз);

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

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

Понятное дело, что у комитета своя работа, у разработчиков компиляторов и стандартных библиотек своя. Понятное-то понятное. Но подзадолбавшее изрядно. Как будто комитет занял позицию “мы только пишем стандарт, к тексту стандарта претензии есть?”

ИМХО, ситуацию можно было бы кардинальным образом переломить, если видоизменить текущую train model, практикуемую комитетом, а именно:

  • когда конкретный пропозал признают пригодным для включения в стандарт, то его не включают в стандарт сразу, а переводят в специальную категорию. Пусть эта категория называется technical specification;

  • пропозалы, перешедшие в категорию technical specification, принимаются компиляторостроителями к реализации;

  • когда technical specification успешно воплощается в жизнь в двух компиляторах, он включается в текст следующего стандарта.

Такая схема преследует две цели:

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

  • выход стандарта означает, что хотя бы где-то фичи из стандарта уже есть.

При этом такой подход не мешает тем счастливчикам, которые могут сидеть на самых-самых свежих версиях компиляторов: как только очередной technical specification оказывается доступен в условном gcc или clang любители “быть на переднем крае” сразу же могут опробовать новую фичу.

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

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

Несколько лет назад, когда в C++20 собирались добавлять модули, уже выдвигалась идея “эпох”. Типа в начале .cpp-файла указывается версия С++, для которой в этом файле пишется код.

Но, к сожалению, эту идею в комитете похоронили.

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

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

Почему это важно?

Потому что, на мой взгляд, в обозримом будущем не будет языка программирования, 100% совместимого с существующим C++ (в том числе позволяющим использовать header-only библиотеки на 3-4-5 этажных шаблонах). Таким языком может быть только сам С++, но очищенный от тех наслоений говна, которые образовались за прошедшие десятилетия.

При этом ключевой возможностью “эпох” должна быть возможность сборки в одном проекте .cpp-файлов с разными эпохами. Грубо говоря, если у нас есть 100500 файлов, написанных еще во времена C++98 и постепенно адаптированных к более-менее современному стандарту, то мы спокойно можем их в этом состоянии и оставить, а не переписывать под новый, улучшенный C++. “Эпохи” должны дать возможность написать и положить рядом еще 100500 файлов, но уже на новых стандартах.

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

Думается, что содержимое .cpp-файла, написанного для свежей “эпохи”, можно транспилировать в код для более старого стандарта, тем самым не нарушая ABI (а ведь это, емнип, и было препятствием к принятию “эпох” в C++).

Еще один механизм исключений

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

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

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

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

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

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

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

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

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

Но зато я убежден в том что:

  • продолжать жить в разделенном надвое мире “C++ без исключений вообще” и “C++ с исключениями” нельзя;

  • полностью отказаться от исключений в пользу каких-нибудь std::expected также нельзя;

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

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

Заключение

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

ИМХО, у C++ сейчас ситуация, как в анекдотах про PL/1 полувековой давности: “У нас получился слишком сложный язык, что мы может с этим поделать? А давайте добавим в него еще пару фич!”

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

Но этого мало. Комитет должен отойти от практики публикации опережающих реальность спецификаций. Вместо того, чтобы декларировать свои фантазии и заставлять обычных разработчиков “от сохи” ждать воплощения этих фантазий в компиляторах, в стандарте нужно фиксировать то, что уже есть и применяется. А смелые фантазии претворять в жизнь через эксперименты на кошках technical specifications.

Оба два эти момента, как по мне, являются ключевыми.

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

PS. Я не тактик, я стратег © поэтому на конкретные вопросы “А как…” ответить смогу лишь фразой “я не тактик, я стратег” 😎