Грокаем C++
Ir al canal en Telegram
Два сеньора C++ - Владимир и Денис - отныне ваши гиды в этом дремучем мире плюсов. По всем вопросам (+ реклама) @ninjatelegramm Менеджер: @Spiral_Yuri Реклама: https://telega.in/c/grokaemcpp Мы на TGstat: https://tgstat.ru/channel/@grokaemcpp/stat
Mostrar más9 386
Suscriptores
+924 horas
+147 días
+1430 días
Archivo de publicaciones
9 386
Идеальная передача из лямбды
#опытным
Мутабельные лямбды позволили нам перемещать захваченные по значению объекты в сторонние функции:
закон стандарт.
Из адекватных примеров явного this на этом все.
Deducing this - одна из мажорных фичей 23-го стандарта. Рано или поздно все на него перейдут и нужно заранее знать кейсы, где фичу можно использовать, чтобы писать более понятный и оптимальный код.
Be a major figure. Stay cool.
#template #cpp23
auto callback = [message=get_message(), &scheduler]() mutable {
// some preparetions
scheduler.submit(std::move(message));
}
Ну а передача копии вообще никогда не была проблемой:
auto callback = [message=get_message(), &scheduler]() {
// some preparetions
scheduler.submit(message);
}
Однако подобную функцию можно использовать в двух контекстах: с возможностью повторного выполнения и одноразового исполнения:
callback(); // retry(callback)
std::move(callback)(); // try-or-fail(rvalue)
Так вот что, если мы хотим в первом случае сабмитить в шедулер копию сообщения, чтобы иметь возможность повторить вызов, а во втором случае - мувнуть сообщение в шедулер. То есть хотелось бы на основании типа ссылочности объекта подстраивать тип поля класса и передавать поле во внутренние вызовы.
Это все можно делать с помощью явного this и std::forward_like:
auto callback = [message=get_message(), &scheduler](this auto &&self) {
return scheduler.submit(std::forward_like<decltype(self)>(message));
};
Пара интересных наблюдений:
👉🏿 Если c std::forward мы могли идеально передать лишь объект замыкания, то с использованием std::forward_like мы можем кастить любой объект к точно такому же ссылочному типу, как и у объекта замыкания. Это позволяет мувать сообщение внутрь шедулера при использовании try-or-fail подхода вызова лямбды.
👉🏿 Можно заметить, что лямбда не мутабельная, хотя в ней возможно изменение объекта message. Это потому что при использовании явного this оператор() у замыкания по умолчанию мутабельный. Таков 9 386
Знакомьтесь — Илья Шишков, Опытный С++, разработчик с 20-летним опытом в IT-индустрии, эксперт по техническим интервью
Профессиональный путь 👇🏻👇🏻👇🏻
🖇11 лет в Яндексе: работал в командах Поиска, Браузера и Яндекс Еды
🖇Текущая позиция: разработчик в R&D команде СУБД Pangolin в СберТехе
🖇Образовательные проекты: создатель онлайн-специализации «Пояса по С++»
За время работы в Яндексе Илья провел более 250 алгоритмических интервью..🚀В своем канале Илья делится 👇🏻 🩵личным опытом прохождения собеседований в различные компании 🩵практическими приемами для успешного прохождения алгоритмических интервью в Яндекс-подобных компаниях👌 Присоединяйтесь, чтобы получить инсайдерскую информацию о мире технических собеседований от человека с реальным опытом по обе стороны стола! ⚡️ https://t.me/+G7trZ7mhkh82MjNi #реклама
9 386
std::forward_like
#опытным
Сегодня рассмотрим функцию-хэлпер, которая поможет нам в рассмотрении одного из юзкейсов применимости deduction this. Их одновременное введение в 23-й стандарт логично, хэлпер дополняет и расширяет применимость deducing this.
Эта функция очень похожа на std::move и, особенно, на std::forward. Она потенциально аффектит только ссылочность типа и может добавлять константности.
Если std::forward объявлена так
template< class T >
constexpr T&& forward(std::remove_reference_t<T>& t ) noexcept;
template< class T >
constexpr T&& forward(std::remove_reference_t<T>&& t ) noexcept;
За счет перегрузок для lvalue и rvalue, она позволяет правильно передавать тип параметра, объявленного универсальной ссылкой, во внутренние вызовы. Здесь задействован всего один шаблонный параметр.
std::forward_like делает шаг вперед. Функция позволяет выполнять идеальную передачу данных на основе типа другого выражения.
template< class T, class U >
constexpr auto&& forward_like( U&& x ) noexcept;
Заметьте, что здесь 2 шаблонных параметра. Мы будем кастить x к ссылочному типу параметра Т.
Зачем вообще так делать?
Без deduction this особо незачем. Но вместе с ним мы можем на основе типа объекта, на котором вызывается метод, идеально передавать данные наружу.
Раньше это было возможно только если бы мы возвращали мемберы объекта. На С++20 это выглядело так:
return forward<decltype(obj)>(obj).member;
Это работало с кучей ограничений. Но с появлением deducing this мы можем делать так:
struct adapter {
std::deque<std::string> container;
auto&& operator[](this auto&& self, size_t i) {
return std::forward_like<decltype(self)>(self.container[i]);
} };
Мы можем из оператора индексации вернуть правую ссылку на строку внутри container, если мы вызываем оператор на правоссылочном объекте. В таком случае объект нам больше не нужен и нет смысла сохранять все его данные. Поэтому можно мувать наружу содержимое контейнера. Ну а если объект адаптера обычный lvalue и не собирается разрушаться, то возвращаем левую ссылку на элемент контейнера.
Более того, с помощью такого приема вообще в принципе появляется возможность использования оператора индексации на rvalue объектах. Если вернуть левую ссылку на содержимое временного объекта, то получим висячую ссылку и UB.
В общем, эта функция разрешает вот такие оптимизации и унифицирует интерфейс для объектов разной ссылочности.
Follow the head. Stay cool.
#cpp23 #template9 386
Удобно сравниваем объекты
#опытным
Иногда нам нужно сортировать объекты кастомных классов. Для этого нам нужно определить оператор<, чтобы объекты могли сравниваться друг с другом. Давайте попробуем это сделать для простой структуры:
struct Time {
int hours;
int minutes;
bool operator<(const Time& other) {
if ((hours < other.hours) || (hours == other.hours && minutes < other.minutes))
return true;
else
return false;
}
};
Выглядит уже довольно сложно. А если мы захотим уточнить класс дополнительным полем секунд? Условие будет просто нечитаемым.
Однако есть элегантное решение этой проблемы. Можно использовать оператор сравнения для тупла. Он работает ровно, как мы и ожидаем в нашем случае. Сравнивает первые поля тупла, если они равны, то сравнивает вторые поля и так далее. В общем, сравнивает свои поля по [короткой схеме](https://t.me/grokaemcpp/187).
Чтобы из наших полей класса получился тупл, нужно использовать функцию std::tie, которая и крафтит кортеж из переданных аргументов. Получится примерно так:
struct Time {
int hours;
int minutes;
bool operator<(const Time& other) {
return std::tie(hours, minutes) < std::tie(other.hours, other.minutes);
}
};
Теперь при добавлении поля класса, мы всего лишь должны добавить аргумент к std::tie:
struct Time {
int hours;
int minutes;
int seconds;
bool operator<(const Time& other) {
return std::tie(hours, minutes, seconds) < std::tie(other.hours, other.minutes, other.seconds);
}
};
Фишка рабочая и удобная. Так что пользуйтесь.
Use lifehacks. Stay cool.
#goodpractice9 386
Ремонтируем серверное железо, слушаем доклады про сердце роботов и работаем в мастерской
Когда-то Яндекс начинался с Поиска, а сегодня в компании полный in-house цикл производства железа: роботы-доставщики, автономный транспорт, умные устройства для дома и многое другое. И для тех, кто разделяет этот инженерный дух и не боится пробовать и создавать, Яндекс проводит Repair Cafe.
В программе:
Мастерская. Сердце всего ивента, где можно починить игровую консоль, разобрать Станцию и посидеть за ардуино в своё удовольствие.
Передвижная выставка. Можно будет изучить (и даже потрогать) роботов, серверы, игровые приставки и не только.
Доклады и воркшопы. От разбора электросамоката до рассказов о том, как устроена схемотехника световых мечей. Будет много практики и инженерных нюансов.
Техносвоп. Здесь можно обменяться неиспользуемыми девайсами с другими участниками.
Не откладывайте — регистрация открыта до 16 апреля.
9 386
Обзор книжки #2
Мы тут недавно провели опрос на канале и выяснилось, что треть наших читателей считают себя новичками, отважно сражающимися с С++, но пока перевес сил не на их стороне. Возможно некоторые из вас только написали знаменитый "hello, world!".
У таких людей особый запрос на хорошие книги, которые помогут им вкатиться в С++.
В первом обзоре на "Практику многопоточного программированния" мы совсем не охватили эту аудиторию, поэтому исправляемся.
Сегодня у нас на обзоре труд Герберта Шилдта "С++ для начинающих".
Все мы знаем, что плюсы - универсальный инструмент, который позволяет писать самое большое множество возможных программ. Но для достижения этого плюсам пришлось разрастись до каких-то монструозных размеров, куда больших, чем госдолг США. Именно поэтому С++ учить сложно. Нужно очень грамотно подбирать подрядок тем, чтобы сложность наращивалась линейно, а не сваливалась на голову неподьемным грузом.
Чудесно, что книга Шилдта реализует именно такой подход. Первое издание вышло в 2002 году, немного после начала эры стандартного С++. Поэтому там просто физически речь не идет о новых стандартах, а только о самой базе С++ и его синтаксических конструкциях: система типов, операции над ними, if'ы, циклы, функции, ООП, шаблоны и исключения. Даже стандартной библиотеки почти не касаются(за исключением iostream, чтобы можно было взаимодействовать с программой).
Как можно говорить в начале книги про std::string, когда вы еще не прошли классы и динамическое выделение памяти? Как можно полноценно рассказывать про new, не пройдя ООП и исключения? Не, ну можно, так многие делают. Только при таком подходе в голове появляется много "черных ящиков", которые работают, но нет понимания как работают. Благодаря намеренному опущению упоминания стандартной библиотеки, текст книги очень последовательный.
"С++ для начинающих" написана очень легким языком. Формат повествования ориентирован на прям "зеленых" человечков. Много пошаговых инструкций с подробными пояснениями, чтобы ваша голова не вспухла от вдруг появившихся 50 строчек кода. После каждой главы даны задания, чтобы закрепить полученные знания.
Единственное, что нужно сделать скидку на год выхода первого издания и не расчитывать на то, что инструменты компиляции, указанные в книге вам помогут написать и запустить вашу первую программу. Тогда мир был другой, динозавры и мамонты еще ходили по земле. Нужно будет искать гайд по запуску с++ кода на вашей ОС во всемирной паутине.
Итого. После прочтения книжки, вы не будете знать, как писать нормальный код на С++. Но это и не было целью. Цель книги - рассказать про базовые синтаксические конструкции языка. То есть по завершении книги у вас будет полноценный фундамент, чтобы изучать уже более продвинутый С++. Постепенность обучения - залог успеха.
Хотите быть успешным в своем пути обучения кунг-фу С++? У меня для вас хорошие новости. От издательства Питер я получил экземпляр этой замечательной книги в печатном виде и хочу его разыграть среди подписчиков и остальных любителей понюхать переплёт.
Все, что нужно сделать, чтобы поучаствовать в розыгрыше - написать один раз в комментариях под этим постом(обязательно) слово "Конкурс". Повторные комментарии будут удаляться. Возможность влететь в розыгрыш будет еще ровно календарную неделю после публикации этого поста. На 8 день выйдет пост с результатами.
Победителя выберем рандомайзером.
Эта книга - прекрасный подарок себе, своему ребенку-старшекласснику или даже продвинутой бабуле, которая хочет хакнуть госуслуги, чтобы накрутить голосов за проведение газа к своей даче. Возможно даже коту. Пусть хоть делом займется, а то только жиреет и спит.
Be lucky. Stay cool
9 386
Mutable lambdas
#опытным
Лямбда выражения имеют одну интересную особенность. И эта особенность аффектит то, что можно делать внутри лямбды.
Простой пример:
int val = 0;
auto lambda1 = [&val]() { std::cout << ++val << std::endl; };
auto lambda2 = [val]() { std::cout << ++val << std::endl; };
Определяем 2 лямбды: в одну захватываем val по ссылке, во второй - по значению.
В чем здесь проблема?
А в том, что во втором случае мы получим ошибку компиляции.
На самом деле operator() у замыкания по умолчанию помечен как const метод, видимо чтобы его можно было вызывать на константных объектах замыкания. То есть это значит, что мы не можем изменять поля замыкания при вызове лямбды.
Ссылки интересным образом это ограничение обходят. Так как ссылки сами по себе неизменяемы(так как по факту это обертка над константным указателем), то формально требования выполняются. А то, что мы изменяем объект, на который указывает ссылка - "вы не понимаете, это другое".
Под одним из прошлых постов разгорелась дискуссия по этому моменту. @KpacHoe_ympo в этом комменте упомянул, что в константных методах можно менять объекты, на которые ссылаются ссылки. Однако на мой вгляд(и подтверждений в стандарте я не нашел), что это не уб. Иначе в лямбду нельзя было бы захватывать ссылки вообще. Вряд ли весь захват по ссылке в лямбду держится на уб.
А вот объекты, захваченные по значению, не умеют обходить ограничения константности. В замыкании они превращаются в обычные поля класса, которые нельзя изменять внутри константных методов.
Но если нам очень нужно изменять захваченные по значению поля? На помощь приходит уже полюбившийся нам mutable. Лямбду можно пометить этим ключевым словом и тогда ее константный оператор() перестанет быть константным! Тогда мы можем как угодно изменять любые захваченные значения:
int val = 0;
auto lambda2 = [val]() mutable { std::cout << ++val << std::endl; };
Теперь все работает отлично.
То есть в лямбда выражениях mutable используется в случаях, когда необходима модификация объектов, захваченных по значению.
Это может использоваться, например, для перемещения захваченных объектов в one-shot коллбэках:
auto callback = [message=get_message, &scheduler]() mutable {
// some preparetions
scheduler.submit(std::move(message));
}
SomeTask task{callback};
task.run();
По завершению таски, коллюэк кладет в шедулер мувнутое сообщения без накладных расходов на копирование.
Мутабельные лямбды - не такая популярная фича, еще менее известная, чем обычный mutable, но о их существовании нужно знать.
Break the rules. Stay cool.
#cppcore9 386
📈Пытаетесь сделать код быстрее и экономичнее? Время научиться использовать плоские контейнеры в C++! На открытом уроке 15 апреля в 20:00 мск мы разберемся, что такое плоские контейнеры!
Что вас ждет:
- Описание плоских контейнеров в C++: какие они бывают и чем отличаются от стандартных.
- Реальные примеры, когда и почему плоские контейнеры — это ваша идеальная пара для эффективной работы с данными.
- Практическая часть: сравнение с традиционными контейнерами и примеры применения на реальных задачах.
Кому будет полезно:
• Программистам C++, работающим с большими объемами данных.
• Разработчикам высокопроизводительных приложений и игр.
• Инженерам, которым нужно минимизировать использование памяти и повысить эффективность.
👉Регистрируйтесь прямо сейчас и получите скидку на большое обучение «C++ Developer. Professional»: https://otus.pw/Hhpt/?erid=2W5zFG7iSBJ
Реклама. ООО "ОТУС ОНЛАЙН-ОБРАЗОВАНИЕ". ИНН 9705100963.
9 386
Mutable. А зачем?
#опытным
В прошлом посте мы рассказали, для чего используется ключевое слово mutable. Однако все же этот инструмент нарушает привычное поведение полей константных объектов. Да, есть семантическая и синтаксическая константность. Но вот проблема: когда у вас в арсенале есть инструмент, который позволяет обходить ограничения, то высока вероятность появления соблазна использовать этот хак не по назначению.
Поля классов в константных методах не должны меняться! Не просто так это правило придумано. В неумелых руках mutable может использоваться, как сглажевание косяков дизайна. В принципе классика: в начале пишется говнокод, потом пишется другой говнокод, чтобы исправить косяки изначального говнокода. Зато быстро задачи закрываются и KPI растет!
Чтобы предотвратить круговорот говнокода в природе, старайтесь минимизировать использование mutable. Проектируйте свои модули с умом, чтобы не приходилось их фиксить грязными хаками.
Тем более, что есть отличный способ, как вы можете заменить использование mutable.
Используйте умные указатели!
Дело в том, что на самом деле при работе с умными указателями вы меняете не сам объект указателя, а объект, на который он указывает. В этом случае вы спокойно можете проводить операции над нижележащим объектом в константном методе и при этом синтаксическая константность будет сохраняться!
Если вам нужен какой-то счетчик определенных событий? Передайте его шаренным указателем в конструктор и инкрементируйте его, сколько вам влезет в константных методах:
class ThreadSafeLogger {
explicit ThreadSafeLogger(std::shared_ptr<CallCountMetric> metric) : call_count{metric} {}
std::shared_ptr<CallCountMetric> call_count;
public:
void log(const std::string& msg) const {
call_count->Increment(); // Works fine
// logging
}
};
Единственное, что будет странно оборачивать мьютексы внутрь умного указателя. Кажется, это более страшная конструкция, чем mutable. Поэтому для мьютексов думаю можно сделать исключение.
В общем, смысл такой, что надо 100 раз подумать о целесообразности использования mutable в вашем конкретном случае. А потом все равно решить его не использовать.
Don't use dirty hacks. Stay cool.
#cppcore9 386
Mutable
#новичкам
Это ключевое слово - один из самых темных уголков С++. И не то, чтобы очень важный уголок. Вы вполне ни разу могли с ним не сталкиваться. Но тем не менее по какой-то причине интервьютеры часто задают вопрос: "для чего предназначен mutable?". Ответит человек или нет особо никак не показывает его навыки программиста, лишь знание узких мест языка. Но раз такие вопросы задают, то вы должны быть готовы к ответу на них. Поэтому и родился этот пост.
Проблема вот в чем. Есть константный объект. Как вы знаете, поля константного объекта запрещено изменять. Но это довольно сильное ограничение. Да, не хотелось бы, чтобы инвариаты класса менялись. Однако помимо комплекса полей класса, представляющих собой инвариант класса, в объекте могут храниться другие поля, которые не входят в этот инвариант.
И вот мы имеем дело с тем, что нам хочется иметь семантическую константность, когда защищаем от изменения только те поля, которые должны быть неизменными в константном объекте. Но по дефолту нам дана синтаксическая константность, которая запрещает изменения любых нестатических полей.
Валера, настало твое время.
Помечая ключевым словом mutable поле класса вы разрешаете менять его в константных методах:
class ThreadSafeLogger {
std::atomic<int> call_count = 0;
public:
void log(const std::string& msg) const {
call_count++; // Error! Changing class field in const member-function
// logging
}
};
В этом логгере мы хотим подсчитать количество логирований на протяжении времени жизни объекта. Одна мы не можем этого сделать, потому что нам запрещено изменять поля в константных методах.
Что же делать?
Вот тут как раз, mutable class ThreadSafeLogger {
mutable std::atomic<int> call_count = 0;
public:
void log(const std::string& msg) const {
call_count++; // Works fine
// logging
}
};
Теперь мы можем изменять счетчик даже в константном методе.
В целом, на это все о функциональности этого ключевого слова.
В каких кейсах его можно применять?
✅ Сбор статистики вычислений в объекте. Пример выше как раз об этом. Для сбора статистики могут использоваться и более сложные сущности, типа оберток над известными системами мониторинга(аля prometheus).
✅ Если вы хотите потокобезопасные константные методы. Вам могут понадобиться мьютексы и/или кондвары, которые придется пометить mutable, чтобы их можно было использовать в константных методах.
✅ Кэш. Результаты предыдущих вычислений никак не влияют на инвариант класса, поэтому внутренний кэш класса можно пометить mutable, чтобы кэш можно было использовать в константных методах.
class SomeComputingClass {
mutable std::unordered_map<Key, Result> cache;
public:
Result compute(const Key& key) const {
if (!cache.contains(key)) {
cache[key] = /* actual computing */;
}
return cache[key];
}
};
Из популярного все. Если кто знает узкий кейсы применения mutable, просим пройти в чат.
Ну все, никакой гадкий интервьюер вас не завалит. Ваше кунг-фу теперь сильнее его кунг-фу.
Surprise your enemy. Stay cool.
#cppcore #interview9 386
Рекурсивные лямбды. Кейсы
#опытным
Сегодня разберем пару кейсов, в которых можно использовать рекурсивные лямбды.
1️⃣ Начнем с очевидного. Где рекурсия, там всегда ошиваются какие-то древовидные структуры. Рекурсивные лямбды могут помочь сделать простые и не очень DFS обходы деревьев.
Можно обходить literaly деревья:
struct Leaf { };
struct Node;
using Tree = std::variant<Leaf, Node*>;
struct Node {
Tree left;
Tree right;
};
template<typename ... Lambdas>
struct Visitor : Lambdas...
{
Visitor(Lambdas... lambdas) : Lambdas(std::forward<Lambdas>(lambdas))...
{}
using Lambdas::operator()...;
};
int main()
{
Leaf l1;
Leaf l2;
Node nd{l1, l2};
Tree tree = &nd;
int num_leaves = std::visit(Visitor(
[](Leaf const&) { return 1; },
[](this const auto& self, Node* n) -> int {
return std::visit(self, n->left) + std::visit(self, n->right);
}
), tree);
}
Наше дерево хранит вариант ноды и листа. И мы можем с помощью паттерна overload обойти все веточки и посчитать листочки.
У вас может возникнуть вопрос: а как мы рекурсивно проходим все варианты лямбдой, которой предназначена только для нод?
Все дело в магии явного this. Здесь мы с вами говорили, что при наследовании и вызове метода базового класса this вывыводится в тип класса наследника. А наш визитор как раз является наследником лямбды, которая обходит ноды дерева. Таким образом мы рекурсивно используем весь визитор.
Можно таким же образом попробовать обходить какие-нибудь джейсоны и другие подобные структуры.
2️⃣ С помощью рекурсивных лямбд можно обходить compile-time структруры, типа туплов(даже вложенных):
auto printTuple = [](const auto& tuple) constexpr {
auto impl = []<size_t idx>(this const auto& self, const auto& t) constexpr {
if constexpr (idx < std::tuple_size_v<std::decay_t<decltype(t)>>) {
std::cout << std::get<idx>(t) << " ";
self.template operator()<idx+1>(t); // Рекурсивный вызов
}
};
impl.template operator()<0>(tuple);
};
std::tuple<int, double, std::string> tp{1, 2.0, "qwe"};
printTuple(tp);
// Output:
// 1 2 qwe
Тут нам придется использовать шаблонные лямбды с индексом текущего элемента тупла в качества шаблонного параметра. Обратите внимание, как вызываются лямбды в данном случае. Так как у нас шаблонный оператор(), то компилятору надо явно сказать, что мы вызываем шаблон и также явно передать в него шаблонный параметр. Подобные лямбды с явным вызовом шаблонного оператора() желательно оборачивать в еще одну лямбду, чтобы коллеги случайно кофеем не подавились, увидев эту кракозябру.
3️⃣ Обход вложенных директорий с помощью std::filesystem:
auto listFiles = [](const std::filesystem::path& dir) {
std::vector<std::string> files;
auto traverse = [&](this const auto& self, const auto& path) {
for (const auto& entry : std::filesystem::directory_iterator(path)) {
if (entry.is_directory()) {
self(entry.path());
} else {
files.push_back(entry.path().string());
}
}
};
traverse(dir);
return files;
};
Ну тут вроде без пояснений все плюс-минус понятно.
Вообще, в любом месте, где применима небольшая по объему кода рекурсия, вы можете использовать рекурсивные лямбды.
Пишите в комменты, если в вас есть что добавить по юзкейсам. Если кто использует какие-то генеративные алгоритмы, для реализации которых подойдет рекурсивная лямбда, тоже пишите. В общем, пишите любые мысли по теме)
Be useful. Stay cool.
#cppcore #cpp23 #template9 386
Устроиться в Яндекс невозможно
Думаю, что вы часто слышали об этом. Также говорят, что после трудоустройства народ там вязнет в бюрократии, перекладывает джейсоны и работает 25/8.
То, что эти утверждения противоречат друг другу - неважно. В принципе понятно, большая компания, о ней ходит много мифов и слухов. Но правда ли все это? Давайте по пунктам.
❗️ Отбор сложнее, чем в космонавты
Летать в огромной центрифуге, как Гагарин, не нужно. Отбор не космически сложный. Есть 2–4 этапа, и к каждому из них ребята из Яндекса помогают подготовиться.
Один раз заботайте алгосы и на 2 года вообще забудете об этих секциях, ведь результаты закрепятся за вашим именем.
❗️Продуктовая разработка без челленджей
Тут очевидно: невозможно работать без челленджей, когда твоими продуктами пользуются десятки миллионов людей.
Разрабы Яндекс Поиска строят систему, которая обрабатывает петабайты информации в день и применяет последние обновы ML.
Поэтому там работают действительно крутые ребята, которые двигают прогресс вперед. Это не rocket science, но продукты там развиваются явно со второй космической скоростью.
❗️ Море легаси-кода
А что такое легаси вообще? Продукты, которые стабильно годами работают? Кажется, что у таких продуктов даже есть чему поучиться.
Вообще, если появилась новая и годная технология, Я первый будет ее тестить и внедрять в продакшен.
❗️ Гигантский энтерпрайз
В любой большой компании много бизнес-процессов. Но в случае Я, они почти не касаются разработки. Каждая команда может сама выбирать архитектуру и стек. Главное, чтобы работало и не ломало общую систему.
Знаменитый принцип "работает, не трогай" там применяют не к коду, а к командам.
❗️ Бесконечные переработки и дежурства
Они есть, никто не спорит, но редкие — в среднем одна неделя раз в три месяца. А если случится инцидент, всегда найдутся отзывчивые коллеги и дадут понятный набор инструкций.
В общем, мифологию оставьте древним грека, викингам и рентвшникам. А если вы всё ещё не верите, попробуйте сами пройти собес в Яндекс. Они как раз ищут бэкенд-разработчиков — вот вакансия.
Check it yourself. Stay cool.
#interview #commercial
9 386
Рекурсивные лямбды. Идеал.
#опытным
Все предыдущие примеры были воркэраундами вокруг неспособности лямбды обращаться к самой себе. Какие-то из них имеют ограничения в использовании, какие-то - накладные расходы.
Но по-настоящему рекурсивные лямбды появились только в С++23 с введением deducing this.
Если лямбда - это класс с методом operator(), значит мы внутрь этого метода можем передать явный this и тогда лямбда сможет вызвать сама себя!
auto factorial = [](this auto&& self, int n) {
if (n <= 1) return 1;
return n * self(n - 1);
};
У нас конечно в С++20 есть шаблонные лямбды, но здесь это немножко оверкилл. Поэтому используем автоматический вывод типа с помощью auto aka дженерик лямбду.
У нас была цель, мы к ней шли и, наконец, пришли. Ура, товарищи, ура!
Однако как будто бы слишком много разговоров о сущности, которой пользовались полтора дровосека.
Да, рекурсивные лямбды - это скорее экзотика. Но и у них есть свои юзкейсы. Поговорим о них в следующем посте.
Find true yourself. Stay cool.
#cppcore #cpp239 386
Рекурсивные лямбды. Хакаем систему
#опытным
Конечно же вчерашний пост был первоапрельской шуткой. Не волнуйтесь, мы вас не бросим и без контента не оставим.
Поэтому возвращаемся к нашим баранам. То есть рекурсивным лямбдам. В прошлый раз мы узнали, что лямбды не могут захватывать себя, поэтому не могут быть рекурсивными. Сегодня поговорим о способах, как обойти эту проблему.
1️⃣ Вместо использования auto, явно приводим лямбду к std::function. Тогда компилятор будет знать точный тип функционального объекта и сможет его захватить в лямбду:
std::function<int(int)> factorial = [&factorial](int n) -> int {
return (n) ? n * factorial(n-1) : 1;
};
Но использование std::function очень затратно по всем критериям. Компиляция ощутимо замедляется, асма намного больше становится, и std::function обычно сильно медленнее обычных функций и лямбд. А еще и динамические аллокации.
Поэтому не самый хороший способ.
2️⃣ Используем С++14 generic лямбды:
auto factorial = [](int n, auto&& factorial) {
if (n <= 1) return n;
return n * factorial(n - 1, factorial);
};
auto i = factorial(7, factorial);
Тут надо разобраться. Мы не могли захватывать лямбду в себя, потому что мы не знали ее тип. Сейчас мы тоже не знаем ее тип, но нам это и не нужно, потому что мы используем дженерик лямбду, которая под капотом превращается в замыкание с шаблонным оператором(). Благодаря cppinsides мы можем заглянуть под капот:
class __lambda_24_20
{
public:
template<class type_parameter_0_0>
inline /*constexpr */ auto operator()(int n, type_parameter_0_0 && factorial) const
{
if(n <= 1) {
return n;
}
return n * factorial(n - 1, factorial);
}
#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline /*constexpr */ int operator()<__lambda_24_20 &>(int n, __lambda_24_20 & factorial) const
{
if(n <= 1) {
return n;
}
return n * factorial.operator()(n - 1, factorial);
}
#endif
};
У класса есть шаблонный оператор, но это полностью завершенный тип. После объявления лямбды компилятор уже знает конкретный тип замыкания и может инстанцировать с ним шаблонный метод.
Форма использования такой лямбды оставляет желать лучшего, потому что нам нужно постоянно передавать ее в качестве параметра. Полечить это, как всегда, можно введением дополнительного уровня индирекции. Обернем лямбду в лямбду!
auto factorial_impl = [](int n, auto&& factorial) {
if (n <= 1) return n;
return n * factorial(n - 1, factorial);
};
auto factorial = [&](int n) { return factorial_impl(n, factorial_impl); };
auto i = factorial(7);
Теперь не нужно передавать доп параметры.
3️⃣ Если лямбда ничего не захватывает, то ее можно приводить к указателю на функцию. На этом основан следующий метод:
using factorial_t = int(*)(int);
static factorial_t factorial = [](int n) {
if (n <= 1) return n;
return n * factorial(n - 1);
};
auto i = factorial(7);
Статическая локальная переменная видна внутри лямбды, поэтому такой трюк прокатывает.
Если у вас есть какие-то еще подобные приемы - пишите в комменты.
Но это все какие-то обходные пути. Хочется по-настоящему рекурсивные лямбды.
И их есть у меня!
Об этом в следующий раз.
Always find a way out. Stay cool.
#template #cppcore #cpp11 #cpp149 386
Чемпионат для подростков по 14 направлениям от «Алабуга Политех»☺️
Программирование и Битва роботов, Экономика и Юриспруденция, 3D моделирование и Английский язык и многое другое, чтобы каждый нашел свою дисциплину.
Для участия тебе нужно☺️
☺️Оставь заявку на сайте😀
☺️Пройди заочный этап на HR-платформе: Business Cats до 1,0 по «Общению» и «Аналитике» для оплаты дороги туда и обратно😀
☺️Приезжай на чемпионат😀
Мы предлагаем тебе☺️
☺️Общий призовой фонд турнира составляет 450 000 рублей😀
☺️Проживание и дорога бесплатно😀
☺️Возможность поступить в «Алабуга Политех»😀
Участвуй в турнире от образовательного центра мирового уровня и получай призы и преимущество в поступлении☺️
9 386
Чемпионат для подростков по 14 направлениям от «Алабуга Политех»☺️
Программирование и Битва роботов, Экономика и Юриспруденция, 3D моделирование и Английский язык и многое другое, чтобы каждый нашел свою дисциплину.
Для участия тебе нужно☺️
☺️Оставь заявку на сайте😀
☺️Пройди заочный этап на HR-платформе: Business Cats до 1,0 по «Общению» и «Аналитике» для оплаты дороги туда и обратно😀
☺️Приезжай на чемпионат😀
Мы предлагаем тебе☺️
☺️Общий призовой фонд турнира составляет 450 000 рублей😀
☺️Проживание и дорога бесплатно😀
☺️Возможность поступить в «Алабуга Политех»😀
Участвуй в турнире от образовательного центра мирового уровня и получай призы и преимущество в поступлении☺️
9 386
Больше нет сил
Ребят, плохие новости. У нас больше нет возможности вести канал. Денис лидит 2 команды и строит себе сам дачу, а я пилю курс на Яндекс Практикум и подрабатываю курьером в самокате. В одного уже все руки в дырках от гвоздей, у второго ноги сточились. * Если у кого есть хорошая бригада строителей в Нижнем Новгороде или остеопат - пишите в личку.
В общем, на канал времени не остается совсем. Чем дольше мы его пилим, тем больше понимаем, что не вывозим. Это нормально, это жизнь. Я вон 20 лет лет трехметровым был, но плита жизни придавила и уполовинила. А теперь я маленький и очень толстый.
Поэтому мы заканчиваем с каналом. Спасибо всем, что читали нас и давали свою обратную связь. Мы очень сильно выросли благодаря вам.
Но есть и радостные новости - тому, кто наберет больше всех сердечек на любом своем комментрии под этим постом, мы передадим права на владение каналом. Уверен, что у вам небезразлично будущее Грокаем С++, поэтому лайкайте достойных людей.
На этом все. Спасибо за этот путь, он был бесценен....
Stay alert. Stay cool.
9 386
Рекурсивные лямбды. Невозможно?
#новичкам
Лямбды по сути - функциональные объекты. Можем ли мы вызвать лямбду внутри самой себя? То есть существуют ли рекурсивные лямбды?
int main() {
auto factorial = [&factorial](int n) {
return n > 1 ? n * factorial(n - 1) : 1;
};
return factorial(5);
}
Вот мы пытаемся с помощью лямбды посчитать факториал числа. В чем здесь проблема?
Фактически данный код значит, что компилятор должен сгенерировать замыкание и в поля этого замыкания поместить ссылку на само замыкание:
class lkdlkhbahbahkl_danfaksdf_lamba
{
public:
int operator()(int n) const
{
return n > 1 ? n * factorial(n - 1) : 1;
}
private:
???? factorial;
};
В этом случае нужно указать тип factorial, но он еще не известен. Он будет известен только после генерации замыкания. А при попытке сгенерировать замыкание... Ну вы уже знаете, что будет.
В общем влипли мы в то, что рекурсивные лямбды невозможны из-за рекурсии.
Однако если в таком виде мы не можем писать рекурсивные лямбды, это не значит, что ни в каком другом виде мы это делать не сможем. Об этом следующие посты.
Don't close on yourself. Stay cool.
#cppcore9 386
Неочевидное преимущество шаблонов
#новичкам
Давайте немного разбавим рассказ о фичах 23-го стандарта чем-нибудь более приземленным
Мы знаем, что шаблоны используются как лекарство от повторения кода, а также как средство реализации полиморфизма времени компиляции. Но неужели без них нельзя обойтись?
Можно и обойтись. Возьмем хрестоматийный пример std::qsort. Это скоммунизденная реализация сишной стандартной функции qsort. Сигнатура у нее такая:
void qsort( void *ptr, std::size_t count, std::size_t size, /* c-compare-pred */* comp );
extern "C" using /* c-compare-pred */ = int(const void*, const void*);
extern "C++" using /* compare-pred */ = int(const void*, const void*);
Как видите, здесь много void * указателей на void. В том числе с помощью него достигается полиморфизм в С(есть еще макросы, но не будем о них).
Как это работает?
Функция qsort спроектирована так, чтобы с ее помощью можно было сортировать любые POD типы. Но не хочется как-то пеерегружать функцию сортировки для всех потенциальных типов. Поэтому придумали обход. Передавать void указатель, чтобы мочь обрабатывать данные любых типов. Но void* - это нетипизированный указатель, поэтому фунции нужно знать размер типа данных, которые она сортирует, и количество данных. А также предикат сравнения.
Вот тут немного поподробнее. Предикат для интов может выглядеть примерно так:
[](const void* x, const void* y)
{
const int arg1 = *static_cast<const int*>(x);
const int arg2 = *static_cast<const int*>(y);
const auto cmp = arg1 <=> arg2;
if (cmp < 0)
return -1;
if (cmp > 0)
return 1;
return 0;
}
Предикату не нужно передавать размер типа, потому что он сам знает наперед с каким данными он работает и сможет закастить void* к нужному типу.
Вот в этом предикате и проблема. Функция qsort не знает на этапе компиляции, с каким предикатом она будет работать. Поэтому компилятор очень ограничен в оптимизации этой части: он не может заинлайнить код компаратора в код qsort. На каждый вызов компаратора будет прыжок по указателю функции. Это примерна та же причина, по которой виртуальные вызовы дорогие.
Тип шаблонных параметров, напротив, известен на этапе компиляции.
template< class RandomIt, class Compare >
void sort( RandomIt first, RandomIt last, Compare comp );
Значит код компаратора шаблонной функции может быть включен в код сортировки. Именно поэтому функция std::sort намного быстрее std::qsort при включенных оптимизациях(а без них примерно одинаково)
Казалось бы плюсы, а быстрее сишки. И такое бывает, когда используешь шаблоны.
Use advanced technics. Stay cool.
#template #goodoldc #goodpractice #compiler9 386
💼Хотите стать востребованным разработчиком на C++?
C++ — это язык, который стоит за самыми мощными приложениями, играми и программами для «железа». Без него никуда. Но вот вопрос: готовы ли вы выйти на уровень Middle Developer за 12 месяцев? 🤔💪
💡На курсе от OTUS вы:
— Изучите C++ с нуля до продвинутого уровня.
— Освоите работу с многопоточностью, памятью, STL и Boost.
— Создадите проекты, которые впечатлят на собеседовании.
❓Что дальше?
Сможете претендовать на позиции Junior+ и Middle.
Получите навыки работы с реальными кейсами и библиотеками.
Овладеете CI/CD, NoSQL и асинхронным программированием.
👉Успейте записаться до старта курса и получите скидку до 15% по промокоду CPP_03: https://otus.pw/ABA0/
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru
¡Ya disponible! Investigación de Telegram 2025 — los principales insights del año 
