C++95 – Telegram
C++95
1.52K subscribers
3 photos
21 files
115 links
640K ought to be enough for anybody

Author: @cloudy_district aka izaron.github.io
Download Telegram
C++95
#longread У std::unique_ptr есть минус - он аллоцирует память в куче. А что, если разработать его аналог с памятью на стеке?.. Об этом можно прочитать тут! https://habr.com/ru/post/665632/
#madskillz

Fast Pimpl

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

В комментариях затронули тему Fast Pimpl (понятия, не сильно связанного со статьей). Это как раз жутко боянистая концепция.

PImpl используется, чтобы скрыть детали реализации и/или ускорить компиляцию. До его использования код в хидере выглядит так:
#include <third_party/json.hpp>
struct Value {
third_party::Json data_;
};

Стандартный подход заключается в замене T на std::unique_ptr<T>, потому что он разрешает использовать incomplete class:
namespace third_party { struct Json; }
struct Value {
std::unique_ptr<third_party::Json> data_;
};

Проблема, которая из-за этого возникает - аллокация объекта в куче, это замедляет рантайм PImpl-ового варианта Поэтому используется подход Fast Pimpl:
struct Value {
std::aligned_storage<sizeof(T), alignof(T)> data_;
};
(вместо sizeof(T) и alignof(T) надо руками ввести нужную цифру)

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

В интернете есть много реализаций этой идиомы (по гуглежу "Fast Pimpl"), и есть обёртки, которые хороши собой 🍬
👍3
#compiler

extern "C" - на что в действительности это влияет?

Примерно 9 лет назад я пробовал писать игры на C++. Для скриптов я использовал Lua. Он поставлялся в виде статической библиотеки (lua.lib для Windows) и header-файлов. Так как Lua написан на C и lua.lib собран компилятором C, то чтобы все слинковалось, надо было подключать хидеры так:

extern "C" {
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}

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

В языке C перегрузки методов нет, и в объектный файл попадает "чистое" название метода. В C++ попадает "запутанное", это называется name mangling:
void hello(int, char) -> hello (в С)
void hello(int, char) -> _Z1helloic (в C++)

extern "C" нужен как раз для того, чтобы методы и переменные, объявленные внутри него, имели "чистое" имя (код на godbolt)

Однако что в остальном? Сам код внутри extern "C" компилируется как C++ или как C? Мнение "C это подмножество C++" ошибочно: два языка развиваются независимо. В C есть то, чего нет в C++, например VLA.

Сам Стандарт показывает примеры, из которых видно, что код скомпилируется по правилам C++: [dcl.link] (там есть классы).

Погрепав исходники компилятора, можно точно сказать, что компиляция внутри extern "C" происходит по правилам C++, и аффектится только условие "надо манглить или нет": исходник Clang.

Вывод из этого такой: хидеры библиотеки на C должны быть достаточно простыми, чтобы в C и в C++ они компилировались одинаково.
👍8
#books

Обзор книги "Advanced C and C++ Compiling" 📚

(можно скачать тут - https://news.1rj.ru/str/progbook/6109)

Книги по программированию часто разочаровывают. Они подобны пирамиде - большую долю занимает элементарщина и копипаст документации в переводе Гоблина; в середине находятся менее боянистые книги, но их меньше; и на вершине находится что-то действительно интересное.

Эта книга, которую я читал в прошлом году, имеет очень большую редкость.
В ней описаны роли линкера (linker) и загрузчика (loader) и их работа в очень глубоких деталях (а также компилятора (compiler), но в меньшей степени). Есть много картинок, описаний форматов, команд Linux и Windows, и прочего.

Книга подробная вплоть до того, что объясняет, по какой схеме надо правильно версионировать свои динамические библиотеки! 🤯 (понятно, что это очень специфическое занятие)
Я бы рекомендовал для чтения первые несколько глав, а также главы 12 и 13 (для Linux-разработчиков)
3👍1
#story

Как готовятся задачи к олимпиадам по программированию? (Часть 1/2)

Наверное, много кто пробовал решать задачки: на codeforces.com, школьных олимпиадах, ACM ICPC, и в других местах. Большинство людей использует C++ для решения.

Я раньше очень увлекался этим занятием (называется "проблемсеттинг"), и даже эпизодически готовил задачи для codeforces и школьных олимпиад (подготовил примерно 15-20 задач).

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

Покажу на примере одной задачи, как происходит подготовка: Codeforces Round #704 - E. Почти отказоустойчивая база данных
(Эта задача самая сложная в том раунде, но у нее простое описание)
C++95
#story Как готовятся задачи к олимпиадам по программированию? (Часть 1/2) Наверное, много кто пробовал решать задачки: на codeforces.com, школьных олимпиадах, ACM ICPC, и в других местах. Большинство людей использует C++ для решения. Я раньше очень увлекался…
Как готовятся задачи к олимпиадам по программированию? (Часть 2/2)

Подготовка задачи ведется на сайте polygon.codeforces.com. Это самая продвинутая платформа, долгое время была практически единственной. Другие платформы (Яндекс.Контест, CodeChef, etc.) со временем потырили у нее фичи - валидацию, встроенную систему контроля версий, и прочее.

Для программок (генераторы, чекеры, etc.) используется библиотека testlib.h.

Процесс подготовки задачи состоит из этих пунктов (могут идти в произвольном порядке, кроме п.1):

(1) Придумывается не-баянистая идея для задачи в краткой формулировке.

(2) Создается художественное описание задачи в LaTeX - пример, это называется "легенда".

(3) Пишутся генераторы тестов, таких программок может быть несколько - пример 1, пример 2. Они могут запускаться с разными параметрами.

(4) Пишется валидатор, он нужен для проверки, что сгенерированные тесты имеют правильный формат и удовлетворяют ограничениям из условия задачи - пример.

(5) Список тестов создается через движок шаблонов Freemarker. Можно написать скрипт, который потом преобразуется в список запусков генераторов - пример из документации. В нашей задаче 100 тестов - список.

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

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

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

Также примерно 5-15 человек разных рейтингов прорешивают задачу, не зная заранее решения. Они могут придумать неожиданное решение попроще, или заслать неправильное решение - все эти кейсы рассматриваются отдельно.

По закону больших чисел, на одну из десятков задач могут происходить факапы такого рода:

(1) Много участников додумались до решения проще авторского и реальная сложность задачи преувеличена
(2) Задача имеет слабые тесты, из-за чего проходит много "плохих" решений.
(3) Авторское решение неправильное.

Первые два типа факапов это еще ничего, а третий тип это полный треш, и на codeforces контест из-за этого просто отменяют (точнее, делают "нерейтинговым", и участвовать в нем нет смысла).
#creepy и #video

Тупое нововведение в C++23: копирование с помощью auto{}

Что нужно сделать, чтобы скопировать объект, особенно в шаблонном коде? Зная, что объявление переменной с типом auto это всегда копия, можно сделать так:
void func(const auto& something) {
auto copy = something;
use(copy);
}

Что, если мы не хотим объявлять новый объект, а создать копию "на месте"? Тогда можно сделать так (decay нужен потому что исходный тип может быть ссылочным и/или иметь cv-квалификаторы):
void func(const auto& something) {
use(std::decay_t<decltype(something)>{copy});
}

А что, если мы считаем что C++ еще недостаточно сложный? То в С++23 теперь можно делать так:
void func(const auto& something) {
use(auto{copy});
}

Эту информацию я узнал из канала "C++ Weekly": https://www.youtube.com/watch?v=5zVQ50LEnuQ В видео приводится не очень убедительный "мотивационный пример".

Комментарии к видео интереснее самого видео, все люди ловят фейспалм и пишут комментарии как "в чем вообще проблема копировать как раньше", "почему не сделали в виде std::copy", и прочее. В этом я с ними согласен!
#books

Обзор книги "API Design for C++ " 📚

(можно скачать тут - https://news.1rj.ru/str/progbook/3214)

Эту книгу мне когда-то посоветовал тимлид соседней команды. Он от нее был в полном восторге 🤩 Однако у меня впечатления были более сдержанными 🤔

Что такое API? По определению из книги:
An API is a logical interface to a software component that hides the internal details required to implement it.
API окружают нас везде, даже внутри одной программы их несколько. И вот книга рассматривает вопросы дизайна API на C++.

В книге довольно четко можно разделить "общие принципы дизайна API" и "специфические вопросы дизайна API на C++".

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

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

☹️ Книга издана в 2011 году, в ней описываются фичи C++0x (рабочее название стандарта C++11), но какой-то древней редакции. Нет ни слова про auto, руками пишутся std::vector<double>::iterator, и т.д.

☹️ Некоторые параграфы писал Капитан Очевидность, например Avoid #define for constants

☹️ На действительно очень важный вопрос совместимости ABI отведена буквально 1 страница с тривиальностями. Без упоминания тулзов как abidiff и каких-то специфических советов.

☹️ Про статические и динамические библиотеки отведены последние 10 страниц из 450-страничной книги, тоже с тривиальностями.
👍1
#video

Марсианские ужасы 👽

Как программист я часто имею дело с "воздушными замками": пишу код который работает с абстрактными в вакууме понятиями.

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

Первые 15 минут видео составляет унылое обозрение CI и велопарка .bash-скриптов для билда. Оттуда мы узнаем, что по авторитетной оценке анализатора lgtm.com репозиторий является редкой годнотой с качеством C/C++ кода A+.

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

В полном ужасе я обнаружил обзор на ядреную дедову смесь C89 и C++11, а от количества code smell у меня отклеились обои. Я стал свидетелем такого преступного программирования как:

Свои кривые реализации строки и вектора (буфера)
ObjBase::~ObjBase() {} // не делают ~ObjBase() = default
void resetSer(void);
ExternalSerializeBuffer(ExternalSerializeBuffer& other);
ExternalSerializeBuffer(ExternalSerializeBuffer* other);
const char* operator=(const char* src);
(void)snprintf(buffer, size, ...
В коде использование reinterpret_cast в 169 местах
Константы задаются через enum

Я думаю, надо сделать аудит NASA на предмет коррупционного сговора с lgtm.com о качестве кода. А также можно не бояться восстания машин, потому что Терминатор скорее будет страдать деменцией и стрелять себе в ноги, чем в Сару Коннор.

Комментарии к видео интереснее самого видео - работники разных European Space Agency подтверждают экстремистский уровень программирования в "научной" и "государственной" сфере.
#advice

Избыточный const снижает перфоманс

Правильное использование const это большая тема в C++. Самое крутое объяснение я видел на Const Correctness, C++ FAQ, но там не показан один из минусов "избыточного" const.

Пусть у нас есть структура, которая представляет собой API-объект. Содержимое этой структуры не планируется как-то изменять после создания, поэтому с первого взгляда логично, чтобы все поля были объявлены константными:
struct Widget {
const std::size_t radius;
const std::vector<Event> events;
const Element element;
};

Но это плохо, если в коде объекты этого типа перемещаются, хранятся в векторе, и так далее.
Дело в том, что const-поля нельзя мувнуть, поэтому в записи Widget w2{std::move(w1)} поля events и element скопируются.
(пример на godbolt с логами)

Также константность полей часто просто не нужна - для нашей цели достаточно, чтобы константным был сам объект (или ссылка на него), а не его поля.
void Do(const Widget& widget);
👍9
#madskillz и #video

Успех применения идиомы Read-Copy-Update в C++

В видео За полгода - C++ Погода разработчик Яндекс.Погоды Дмитрий Старков рассказывает о переезде погодных микросервисов на C++ 🌤

До переезда Погода разрабатывалась на Node.JS + Memcached и отвечала за 300мс в 99 перцентиле. Это было медленно даже с кэшем, а после улучшения погодных предсказаний кэш стал сильно разъезжаться с реальными данными 👎🏻

Чтобы ускориться и улучшить утилизацию CPU, решили переписать на C++.
Для быстроты данные держатся in-memory, не в СУБД.

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

Обычный подход - shared_ptr:
std::shared_ptr<State> state;

И использование std::mutex с std::lock_guard для безопасного чтения/записи.

Более быстрый подход - использование std::shared_mutex и std::shared_lock (для читателей) и std::unique_lock (для писателей)

Однако такой подход все равно был недостаточно быстрым (в видео есть графики со "спайками"). Поэтому применили lock-free идиому Read-Copy-Update. Состояние живет тут:
std::atomic<std::shared_ptr<State>> state;
И читатель атомарно копирует объект, лежащий внутри std::atomic, прежде чем как-то с ним работать. Писатель также атомарно изменяет объект.

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

Переписывание обернулось успехом - при возросшем (за время переписывания) в 1.5 раза RPS Погода отказалась от рассинхронизирующих кэшей и стала отвечать за 30мс на 99 перцентиле!
👍5😢1
#creepy

Двуликий inline для функций

Так вышло, что сейчас в C++ ключевое слово inline для функций (точнее, само понятие inline-функции) наделяет их двумя абсолютно ортогональными друг другу effective смыслами. Понятно, что один смысл вытек из другого, но все равно это раздельные сущности. Это:

(1) [dcl.inline].2: Подсказка компилятору о том, что более предпочтительна подстановка кода из функции в место вызова, чем сам вызов функции. (В компиляторе Clang в LLVM IR этот атрибут у функции называется inlinehint)

(2) [dcl.inline].6: Правило, что у функции может быть несколько определений (definition) за программу. Для отсутствия UB это должны быть одинаковые определения - что естественным образом соблюдается, т.к. обычно метод определен в хидере. (В компиляторе атрибут у функции называется linkonce_odr)

Получается, что у функции есть два атрибута - inlinehint и linkonce.

Прикол в том, что:
Стандарт написан так, что inlinehint подразумевается только у функций, где явно написан спецификатор inline.

Clang выполняет именно это, т.е. inlinehint ставится тогда и только тогда, когда есть спецификатор inline.

То есть получается вот что: inline int random() { return 4; } будет И inlinehint, И linkonce.

А вот методы, которые по Стандарту являются implicit inline functions, например:
(*) инстанциации шаблонов
(*) default и deleted члены классов, и их неявные методы
(*) методы, чей definition находится внутри definition класса
(*) constexpr- и consteval-функции (на самом деле только constexpr, т.к. consteval "испаряется" и в LLVM IR его код тупо не попадает)

... они только linkonce. Надо все равно писать ключевое слово inline, чтобы было linkonce+inlinehint.

Если бы я мог редактировать стандарт, я бы переименовал inline functions в linkonce functions, потому что текущее описание в Стандарте сильно запутывает...

Интересные вопросы:
(1) Так ли сильно влияет наличие атрибута inlinehint на компиляцию? Я слышал, что компиляторы уже давно не обращают внимание на подобные подсказки...
Влияет практически незаметно. Порог для инлайнинга у обычного метода 225 попугаев, для inlinehint-метода 325 попугаев (ссылка на код), а для реальной разницы нужно ~1000 попугаев.

То есть в 99% кейсов inline constexpr void foo() и constexpr void foo() будут вести себя одинаково, однако в clang-tidy не принимают патч на удаление redundant inline, потому что остается какой-то непонятный 1% кейсов.

(2) Почему один атрибут вытекает из другого, если они ортогональны?
Чтобы компилятор мог в translation unit заинлайнить (inlinehint) функцию, TU нужно "видеть" исходник этой функции, то есть её тело. В общем случае это невозможно обеспечить, потому что если у нас N штук TU, то будет N определений одной и той же функции и линкер сломается. Поэтому эта функция должна являться linkonce, чтобы не нарушился ODR.
👍3🔥3🤔3🤯1
#compiler

Прикладные linkage types в C++ 🔗

Стандартное добавление функции в C++ происходит так: в .h-файле пишется объявление функции (которое попадает в много translation unit), а в одном .cpp-файле пишется определение функции.

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

Посмотрим, каким будет LLVM IR (компиляторное промежуточное представление C++-кода) для int sum1(), static int sum2(), inline int sum3(): ссылка на godbolt.

У LLVM IR есть интересный список linkage types для символов, типов много. Linkage type определяет, как ведет себя символ во время линковки в бинарник. (Символ - это функция или глобальная переменная)

▪️ У sum1 дефолтный linkage type external - возможно ровно 1 определение символа. Линковщик выдаст ошибку, если в программе будет 0 или >1 определения.

▪️ У sum2 linkage type internal - символ доступен только из того translation unit, где определен. Если в процессе линковки попадется одноименный символ, то линковщик просто переименует sum2, чтобы не было коллизии.

▪️ У sum3 linkage type linkonce_odr. Это типичный weak symbol, как и некоторые другие типы (linkonce, weak).
В программе может быть несколько weak-определений одного и того же символа.
Если все определения символа являются weak, то линковщик берет рандомное определение.
Если какое-то из определений символа является strong (как у sum1), то линковщик берет strong-определение.

В чем отличие linkonce от linkonce_odr?
▪️ В общем случае weak-определения могут быть разными, поэтому компилятор не имеет права заинлайнить вызов weak-функции (после линковки у функции может оказаться совсем другое определение)
▪️ Но Стандарт С++ требует от программиста обеспечить, чтобы inline-функции имели идентичное определение (это естественным образом достигается, если определение находится в .h-файле)
▪️ Поэтому компилятор имеет право заинлайнить вызов метода sum3 - программа от этого сломаться не сможет

Скомпилируем объектный файл
> clang++ -c link.cpp
На Linux получим объектный файл формата ELF. Используем утилиту readelf для чтения таблицы символов. Чтобы вывести исходные имена методов (не mangled-имена), используем утилиту c++filt:
> readelf -s link.o | c++filt

Получим такой вывод (опустил другие символы):
Symbol table '.symtab' contains 21 entries:
Num: Value Size Type Bind Vis Ndx Name
6: 00000000000000c0 18 FUNC LOCAL DEFAULT 2 sum2(int, int)
14: 0000000000000000 18 FUNC GLOBAL DEFAULT 2 sum1(int, int)
20: 0000000000000000 18 FUNC WEAK DEFAULT 7 sum3(int, int)

В общем случае выгоднее использовать inline-методы, чем static-методы! Потому что так меньше загрязняется бинарник, и все статические переменные внутри функции живут в количестве 1 штуки, а не в N штук:
inline int* get_address() {
// `dummy` займет sizeof(int) памяти, а со static-методом было бы N*sizeof(int)
static int dummy;
// inline-метод - возвращает один и тот же указатель
// static-метод - уникальный для каждого translation unit
return &dummy;
}
👍9
#madskillz

Обертка над потоком вывода 🌊

У каждой крупной компании (и в FAANG) есть своя реализации std::string и/или разных строковых утилит.

Много где, прежде чем вывести немного видоизмененную строку в поток вывода (это объект с оператором <<, например std::cout, std::stringstream), создается новая строка:

log << "Value is " << valueStr.Quote();

Метод Quote() создаст новую строку с кавычками " по бокам. Такой код встречается тысячами, и вредит перфомансу , но это удобнее, чем выводить бесконечные "\"".

Попробуем сделать по аналогии с std::boolalpha "флаг" для "бесплатного" вывода строки. Мы хотим, чтобы можно было писать так:
log << "Value is " << quote << valueStr;
И это было бы аналогично записи
log << "Value is " << '"' << valueStr << '"';

Как это можно сделать?
Пусть выражение ((объект потока) << quote) вернет объект TQuoteWrapper, а запись ((объект TQuoteWrapper) << str) вернет исходный поток с записанным туда str.

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

inline constexpr struct{} quote;

template<typename TStream>
auto operator<<(TStream& stream, decltype(quote)) {
return TQuoteWrapper{stream};
}

Сам объект TQuoteWrapper имеет оператор <<, в котором записывает то, что нужно:
template<typename TArg>
TStream& operator<<(TArg&& arg) {
return Stream_ << '"' << std::forward<TArg>(arg) << '"';
}
По ссылке на godbolt можно посмотреть, что получилось 👍

Также можно сделать так, чтобы TQuoteWrapper эскейпил символы строки (например, заменял \" на \\\").
По ссылке на godbolt можно посмотреть, как я это сделал.

Другой подход к "бесплатному" выводу строки в нужном формате можно посмотреть в библиотеке abseil - Writing to Stream. В нем потоку отдается "легкий" объект:
std::cout << absl::StreamFormat("\"%s\"", valueStr);
Но есть и свои ограничения - нельзя по-полному перепахать строку (сделать escape символов) через printf-like аргумент
👍9
#compiler и #story

Как написать свой компилятор? ⚙️🛠

По моему мнению, лучший туториал по созданию компилятора своего языка на C++: My First Language Frontend

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

В туториале рассматривается, как написать компилятор для простого тьюринг-полного языка, на котором возможны такие программы:
# Compute the x'th fibonacci number.
def fib(x)
if x < 3 then
1
else
fib(x-1)+fib(x-2)

# This expression will compute the 40th number.
fib(40)

По очереди разбираются типичные вопросы и pipeline:
(1) Лексический анализ - перевод исходного кода в лексемы
(2) Синтаксический анализ - перевод лексем в AST: абстрактное синтаксическое дерево (написание несложного LL(1)-анализатора языка)
(3) Кодогенерация - перевод AST в промежуточное представление LLVM IR; на этом этапе можно узнать теорию о SSA и графе потока управления
(4) Оптимизация кода - включение нескольких оптимизаторов (список оптимизаторов тут) и теория по ним
(5) Компиляция кода в объектный файл
(6) ... и по желанию многие другие вопросы: debug-символы, типы, управление памятью, стандартная библиотека...

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

Мне больше всего понравилась возможность слинковать код на другом языке с кодом на C++: сделал небольшое описание на github.
👍8
#creepy

Можно ли рекурсивно вызывать метод main()? 🔄

(минутка бесполезной информации)

Когда-то давно я читал набор вопросов к собеседованию по C++, и там встретился такой: "можно ли вызывать метод main() из программы"?

int main() {
int n;
std::cin >> n;
if (n != 0) {
return main();
}
return 0;
}

Казалось бы, зачем так делать почему бы нельзя было так сделать?

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

Linkage функции main() является implementation-defined (ссылка на стандарт): обычно имя функции не манглится. Также написано, что main() не должен вызываться в программе.

Стандарт говорит, что рекурсивный вызов main() запрещен (ссылка на стандарт).

Однако все современные компиляторы успешно компилируют такой код! Они могут выводить warning, если компилировать с опцией -Wmain: ссылка на godbolt.

Поэтому ответ на вопрос такой - рекурсивно вызывать main() нельзя, но компилятор будет компилировать это, если не указывать специальные флаги как -Wmain или -pedantic.
👍4🤔1
#books

Обзор книги "C++20: Get the Details" 📚

(можно скачать тут - https://news.1rj.ru/str/progbook/6082)

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

Стандарт C++20 - самый влиятельный после C++11, с кучей новых вещей.

В книге на 530 (!) страниц описываются нововведения, то есть "дифф" между C++17 и C++20. Несмотря на размер книги, воды там нет 🚱 Все написано по делу, прилагаются самые подробные объяснения и примеры кода.

Чувствуется, что в книгу вложено огромное количество труда. Почти все вещи объяснены лучше, чем "в интернете" (на cppreference.com и подобных сайтах). Для "больших" нововведений описываются предпосылки и другая редкая информация.

Книгу можно читать как справочник - интересующую главу за раз.
Некоторые нововведения, к сожалению, еще не поддержаны нормально (как модули), или редко используемы (как корутины), поэтому стоит помнить, что редко используемые фичи забываются быстро.
👍7
#advice

"Это как std::vector, но на 20% круче" 🕶

Часто приходится иметь дело с множеством объектов, и также как-то их переупорядочивать, находить в них объект по ключу, и так далее - в зависимости от бизнес-сценария.

Пусть у нас есть объекты класса Meal (блюда ресторана 🍝), тогда обычно несколько блюд представляют так:
std::vector<Meal> meals;

И также где-то находятся методы, которые делают то, что нужно
void SortAlphabetically(std::vector<Meal>& meals);
void SortByCostDesc(std::vector<Meal>& meals);
const Meal* FindMealById(const std::vector<Meal>& meals, std::string_view id);

Это подход уменьшает читабельность кода, потому что множество объектов живет как бы в отрыве от своих методов.

Я несколько раз успешно применял подход - унаследоваться от std::vector и определить там все нужные методы. Получается примерно так:
class MealList : public std::vector<Meal> {
public:
static MealList ParseFromJson(const nlohmann::json& json);

void SortAlphabetically();
void SortByCostDesc();
const Meal* FindMealById(std::string_view id) const;

private:
MealList() = default;
};
И определения методов находятся в соответствующем .cpp-файле.

MealList держит все методы "при себе" и при этом сохраняет всю остальную семантику std::vector (например, возможность range-based for loop).
#story #retro

Путь Александра Степанова - автора STL в C++ 🔆

Для современных детей смартфоны существовали всегда, и кнопочные телефоны - это что-то древнее и не стоящее внимания, а проводные телефоны - и вовсе атрибут фильмов про СССР.

Так же для программистов, которые начали свой путь 10-15 лет назад, STL существовал всегда. Однако STL имеет интересную историю.

Я наткнулся на страницу в Википедии про Александра Степанова - автора STL и прочитал ее, много интервью с ним, и некоторые его книги. Коллекция этих материалов есть на stepanovpapers.com.

У Александра, на мой взгляд, биография начинается крайне необычно:
⬛️ Родился в 1950 году в Москве
⬛️ В 1972 году закончил мехмат МГУ
⬛️ С 1972 по 1976 год работал программистом (разработка мини-компьютера для управления гидроэлектростанциями)
⬛️ В 1977 году эмигрировал в США

Я слабо представляю себе, сколько в 1972 (!) году в мире было полноценных программистов, которые реально писали свои дебаггеры, линкеры, операционные системы реального времени. Наверное, в то время это были единичные специалисты.

Также мне было интересно, каким образом можно было эмигрировать из СССР в США в 1977 году с таким важным бэкграундом, учитывая истории про необычные побеги в те годы. К сожалению, ни в одном интервью этот вопрос не затрагивается 🙁 Лишь какие-то избитые фразы как в интервью 2003 года:
Я покинул СССР, потому что не любил советскую власть, а Microsoft - это советская власть в программировании.

Однако вернемся к интервью и книгам. Любое интервью будет интересным, потому что раскрывает какие-то малоизвестные факты. Александр вообще создатель обобщенного программирования как такового, до C++ он реализовал этот подход в языках Scheme и Ada.

Можно почитать это интервью - там присутствуют приколы, например вопрос «расшифровывается ли STL как "Stepanov and Lee"?»; а также факт, что первые идеи про обобщенное программирование пришли Александру в 1976 году в полубессознательном состоянии, когда он находился в больнице с пищевым отравлением.
Также в интервью Александр высказывает мнение о ненужности ООП и Java (определенный как money oriented programming (MOP))

Из книг мне больше понравился Notes for the Programming course at Adobe 2005-2006.
На первых страницах Александр описывает, как он в 1972-1976 годах сам себя научил принципам хорошего кода.
Сначала он писал проект (например, дебаггер), через несколько месяцев переписывал его с нуля с знанием всех ошибок дизайна, затем это повторялось, и так далее.
Затем он писал проекты не как попало, а с учетом принципов (размер функции не более 20 строк, выделение общего кода в общие функции и т.д.). Этот эксперимент был крайне удачным и упростил разработку.

Александр заметил, что чем больше программа, тем меньше у нее application-specific (или non-general) кода и больше general кода.
В современных десктопных приложениях содержание non-general кода намного меньше чем 1%. Сейчас это самоочевидно, если посмотреть на объем кода операционной системы и Qt/WinForms. Но в то время это было далеко не так очевидно, потому что разработка велась очень близко к "железу".

Сейчас обобщенное программирование - обычное дело, но в те времена это было довольно контркультурное движение. Оно не соответствует концепции "top-down design", потому что это утверждение о том, что можно хорошо спроектировать какой-то компонент без особых знаний о том, как этот компонент будет использоваться 🤔

Из упомянутой выше книги можно прочитать первые несколько глав - там находится очень редкая информация о том, как правильно дизайнить обобщенные классы (как std::vector), и о том, как должны были бы выглядеть разные места C++ с точки зрения Александра (но Страуструп запретил). Потом в книге начинается переизобретение std::move (книга 2006 года), и это читать не имеет смысла.
👍7
#story #carbon

Язык Carbon как наследник С++ - о чем молчат его авторы? 🕵️

Многие уже слышали о языке Carbon, который Google представляет как "наследник C++". Всего лишь за несколько дней туда уже наставили 16k лайков.

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

🤔 Почему его делают?
По словам авторов, основной проблемой C++ является огромная сложность внесения в него изменений.

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

Окончательный баттхерт произошел в феврале 2020 года, когда комитет отказался ломать ABI (подробнее об этом можно почитать тут), тогда стало ясно что C++ продолжит развиваться черепашьим темпом, и Google стал подпольно создавать свой C++ 2.0

🤔 Что в нем планируется?
Самое главное - то, что C++ и Carbon могут подключать хидеры друг друга, то есть в наличии двунаправленная совместимость языков (как между Java и Kotlin). Я подозреваю, что это сделано через патч в Clang, который при виде файла с нужным расширением по-нужному парсит его в промежуточное представление, а дальше всё вместе оптимизируется.

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

(*) около языка: менеджер пакетов, сильная стандартная библиотека (если там будет как в Python, то я балдю бом бом), быстрый процесс "стандартизации", и прочее...

(*) сам язык: pattern matching (как в Rust), рефлексия (пока доки нет, но будет), traits (тоже как в Rust), работа с типами, в будущем memory safety, и прочее...

(*) полно мелочей: адекватная иерархия наследования, классы final по умолчанию, проверка на NRVO, и прочее...

🤔 Что может быть дальше?

Здесь я опишу факты, в которых я +- уверен, а выводы на их основе можно сделать самому 👻

(1) У Google есть гигантский админресурс, чтобы продвинуть любой нужный партии язык в массы - реклама на C++-конвенциях, массовый выпуск курсов (например на Coursera), обучение в вузах...

Так что при желании язык можно продвигать "нерыночными" способами (вряд ли Go выстрелил бы сам в "дикой природе").

(2) У Google есть технический ресурс, чтобы создать C++ 2.0 и не сильно обоср*ться, потому что многие очень активные контрибьюторы в Clang и в Стандарт C++ работают в Google.

(3) Самое печальное: "Экономика" развития C++ и Carbon будет кардинально отличаться.

Дело в том, что если в C++ контрибьютят в основном "подвижники", крайне заинтересованные в основном нематериально люди, то Carbon разрабатывается корпорацией.

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

Поэтому есть риск, что из-за личных планов "выкатить что-то крутое, показать манагеру, и пойти на повышение" Carbon может проэволюционировать в какое-то болото.
👍8😢2🤔1