ar
Feedback
Грокаем C++

Грокаем C++

الذهاب إلى القناة على Telegram

Два сеньора C++ - Владимир и Денис - отныне ваши гиды в этом дремучем мире плюсов. По всем вопросам (+ реклама) @ninjatelegramm Менеджер: @Spiral_Yuri Реклама: https://telega.in/c/grokaemcpp Мы на TGstat: https://tgstat.ru/channel/@grokaemcpp/stat

إظهار المزيد
9 384
المشتركون
+924 ساعات
+147 أيام
+1430 أيام
أرشيف المشاركات
Какой результат попытки компиляции и запуска кода?
Anonymous voting

Квиз В тему индексирования многоразмерных массивов закину сегодня #quiz. Ничего супер запутанного, но от этого не менее интересно. Вот мы в прошлых постах говорили, что начиная с С++23 мы можем определять оператор[], который принимает несколько аргументов. А что будет, если мы просто возьмем и создадим многомерный массив, как нам бы это было удобно? И будет получать доступ к его элементам:
#include <iostream>

int main() {
  auto array = new int[10, 20]{10};
  std::cout << array[1, 0] << " " << array[11, 1] << std::endl;
}
Видели когда-нибудь такое? Даже если нет, у меня к вам всего один вопрос. Какой будет результат попытки компиляции и запуска этого кода ?

​​std::mdspan #опытным "Я понял, что можно перегружать оператор[] для разного числа аргументов. Но это только для моих классов. А что делать со стандартными контейнерами типа std::vector? Могу я как-то на нем использовать многоаргументный оператор, если по факту я храню в нем матрицу?" И нет, и да. Интерфейс семантически одномерного контейнера никто менять не будет. Однако вместе с С++23 появился еще один полезный класс std::mdspan. Это фактически тот же std::span, то есть это view над одномерной последовательностью элементов, только он интерпретирует ее, как многомерный массив. То есть вы теперь буквально можете интерпретировать свой std::array или std::vector, как многомерный массив. И! У std::mdspan переопределен оператор[], который может принимать несколько измеренений и выдает ссылку на соответствующий элемент.
std::vector v{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
 
// View data as contiguous memory representing 2 rows of 6 ints each
auto ms2 = std::mdspan(v.data(), 2, 6);
// View the same data as a 3D array 2 x 3 x 2
auto ms3 = std::mdspan(v.data(), 2, 3, 2);

// Write data using 2D view
for (std::size_t i = 0; i != ms2.extent(0); i++)
  for (std::size_t j = 0; j != ms2.extent(1); j++)
    ms2[i, j] = i * 1000 + j;

// Read back using 3D view
for (std::size_t i = 0; i != ms3.extent(0); i++)
{
  std::println("slice @ i = {}", i);
  for (std::size_t j = 0; j != ms3.extent(1); j++)
  {
    for (std::size_t k = 0; k != ms3.extent(2); k++)
      std::print("{} ", ms3[i, j, k]);
    std::println("");
  }
}
Вывод:
slice @ i = 0
0 1
2 3
4 5
slice @ i = 1
1000 1001
1002 1003
1004 1005
В этом примере мы интерпретируем один и тот же массив, как матрицу и как такую кубическую структуру. Ну и играемся с выводом, чтобы продемонстировать, что мы реально можем манипулировать многомерной структурой, как хотим. В начале заполняем массив, как матрицу с двумя строчками(значения в строчках отличаются на 1000). Дальше читаем массив, как 3-хмерную структуру 2х3х2. Разрезаем ее на 2 части и получаются две матрицы 3х2, которые и выводим на экран. Для создания mdspan нужно передать в конструктор начальный итератор и последовательные размерности. Их может быть сколько угодно. Число элементов или последний элемент последовательности не нужны, так как набор размерностей однозначно задает число элементов. Метод extend возвращает размер вьюхи по заданному ранк индексу. Так что скоро можно даже будет обойтись без сооружения своих оберток над стандартными контейнерами для получения доступа к многомерному оператору[]. И использовать стандартный инструмент std::mdspan. Use standard things. Stay cool. #cpp23 #STL

Владимир, вы уволены! Вместо вас мы взяли нейросеть! Рынок IT скоро захватят ChatGPT и Gemini. А они появились лишь в 2022 го
Владимир, вы уволены! Вместо вас мы взяли нейросеть! Рынок IT скоро захватят ChatGPT и Gemini. А они появились лишь в 2022 году. Станьте тем, кто получает выгоду с  ИИ. Как их использовать для кода подробно рассказывают на канале Artificial Intelion. Тут каждый день делают обзоры на нейросети и их применение в IT, делятся лучшими инструментами для программистов и показывают как их внедрить. Переложите свои задачи на ИИ, чтобы не просрачивать дедлайны - @artificial_intelion

​​Доступ к элементам многомерных структур #опытным Если вы спросите разработчиков C++ о том, как они получают доступ к элементам многомерных массивов, скорее всего, вы получите несколько различных ответов в зависимости от их опыта. Если вы спросите кого-то, кто не очень опытен или работает в нематематической области, есть большая вероятность, что ответ будет таким, что вы должны использовать несколько операторов[] подряд: myMatrix[x][y]. Есть несколько проблем с таким подходом: ⛔️ Это не очень удобно чисто внешне. Все номальные люди используют синтаксис [x, y]. ⛔️ Это работает "из коробки" на реально многомерных структурах, типа вложенных массивов(типа вектора векторов). Чтобы поддержать даже такой синтаксис для кастомных классов, придется несколько приседать. ⛔️ Поэтому многие находят лазейки, чтобы делать что-то похожее на [x, y], этих лазеек много, нет какого-то стандарта. ⛔️ Стандарт использует operator[] с одним аргументом для получения доступа к элементам массивов. ⛔️ Лазейки неконсистентны с одноразмерными массивами в плане получения доступа к элементам. ⛔️ Некоторые из них преполагают спорную семантику, а некоторые делают практически нечитаемыми сообщения об ошибках компиляции. ⛔️ Возможные проблемы с инлайнингом. Рассмотрим лазейки в будущем, а сейчас сфокусируемся на решении проблемы. В С++23 наконец завезли многоаргументный operator[]. Теперь при проектировании своей матрицы или даже тензора перегружать оператор[] для 1, 2, 3 и более входных аргументов. Так для матрицы можно возвращать элемент, если мы передали 2 размерности, или возвращать всю строку, если мы передали только одну размерность.
template <typename T, std::size_t ROWS, std::size_t COLS>
class Martrix {
    std::array<T, ROWS * COLS> a;
public:
    Martrix() = default;
    Martrix(Martrix const&) = default;
    constexpr T& operator[](std::size_t row, std::size_t col) { // C++23 required
        assert(row < ROWS and col < COLS);
        return a[COLS * row + col];
    }
    constexpr std::span<T> operator[](std::size_t row) {
        assert(row < ROWS);
        return std::span{a.data() + row * COLS, COLS};
    }
    constexpr auto& underlying_array() { return a; }
};
 
int main() {
    constexpr size_t ROWS = 4;
    constexpr size_t COLS = 3;
    Martrix<char, ROWS, COLS> matrix;
    // fill in the underlying 1D array
    auto& arr = matrix.underlying_array();
    std::iota(arr.begin(), arr.end(), 'A');
    for (auto row {0U}; row < ROWS; ++row) {
        std::cout << "│ ";
        for (auto col {0U}; col < COLS; ++col) {
                std::cout << matrix[row, col] << " │ ";
        }
        std::cout << "\n";
    }
    std::cout << "\n";
    auto row = matrix[1];
    for (auto col {0U}; col < COLS; ++col) {
        std::cout << row[col] << ' ';
    }
}
Здесь мы создали матрицу 4х3, заполнили ее буквами алфавита и вывели на экран каждый элемент через matrix[x, y]. А также дальше получили целую строку матрицы через matrix[x] и вывели ее содержимое на экран:
│ A │ B │ C │ 
│ D │ E │ F │ 
│ G │ H │ I │ 
│ J │ K │ L │ 

D E F
В качестве обертки для строки используем std::span из С++20. Очень красиво и удобно. Разработчикам математических библиотек сделали большой подарок. Be consistent. Stay cool. #cpp23 #cpp20

​​Фикс баги с инициализацией инта В прошлом посте говорили об одной неприятности при использовании универсальной инициализации интов. При таком написании: auto i = {0}; i будет иметь тип std::initializer_list<int>. С++17 исправил такое поведение. Но для полного понимания мы должны определить два способа инициализации: копирующая и прямая. Приведу примеры
  auto x = foo();  // копирующая инициализация
  auto x{foo()};  // прямая инициализация, 
//                   проинициализирует initializer_list (до C++17)
  int x = foo();  // копирующая инициализация
  int x{foo()};  // прямая инициализация
Для прямой инициализации вводятся следующие правила: • Если внутри скобок 1 элемент, то тип инициализируемого объекта - тип объекта в скобках. • Если внутри скобок больше одного элемента, то тип инициализируемого объекта просто не может быть выведен. Примеры:
auto x1 = { 1, 2 }; // decltype(x1) -  std::initializer_list<int> 
auto x2 = { 1, 2.0 }; // ошибка: тип не может быть выведен, 
//                      потому что внутри скобок объекты разных типов
auto x3{ 1, 2 }; // ошибка: не один элемент в скобках
auto x4 = { 3 }; // decltype(x4) - std::initializer_list<int>
auto x5{ 3 }; // decltype(x5) -  int
Этот фикс компиляторы реализовали задолго до того, как стандарт с++17 был окончательно утвержден. Поэтому даже с флагом -std=c++11 вы можете не увидеть некорректное поведение. Оно воспроизводится только на древних версиях. Можете убедиться тут. Fix your flaws. Stay cool. #cpp11 #cpp17 #compiler

Время от времени в сети появляются комментарии и персонажи, которые продвигают нарротив, что алгосы не нужны. Я понимаю их. Приходишь на собес и тебе предлагают решить совсем какую-то оторванную от реальности задачу. "Обведите острова на карте". Это почти смешно. Когда и кому эти навыки пригодятся - не понятно. Однако не все лежит на поверхности. На собеседовании интервьюеры решают очень важную и нетривиальную задачу - за очень ограниченное время понять, справится ли человек с будущими задачами или нет. И нужен инструмент, который поможет это проверить. Именно таким инструментом и являются алгосы. Популярно объясняю: 👉🏿 Знания алгоритмов и структур данных - база компьютер сайенса. Без нее в плюсах особо делать нечего. Мы часто работаем на низких уровнях абстракций и высоких нагрузках, где знания базы просто жизненно необходимы. 👉🏿 Кандидату нужно абстрактный текст задачи уметь перенести на язык компьютерных сущностей. Это очень важный навык. Бизнес приносит задачи в формате "мне нужно клиенту отправлять пиццу квадракоптером". И важным этапом работы будет перенос этих слов на язык кода. Тут и требования надо уточнять, и в целом понимать, чего от тебя хотят (не все понимают). 👉🏿 Компьютерные сущности нужно правильно уметь перенести в код на конкретном языке. Тут хоть базово, но проверяются навыки работы непосредственно с языком. 👉🏿 Кандидату нужно убедиться, что алгоритм работает. Для этого он должен уметь тестировать свой код, в том числе и устно. Тесты в проекты нужны, никто с этим спорить не будет. Но вот прилетела вам бага с прода. И иногда ее сложно вопроизвести. Чтобы побороть багу нужно вычитывать много кода и пытаться найти в нем ошибки чисто эмперически. Или написать дополнительные тесты. Так что устно проверить свой код на ошибки - это такая демо-версия обычной рабочей рутины и стрессовых событий. Можно перечислять еще много. Но если все эти навыки нужны работодателю, то они нужны и нам всем. Алгоритмы нужны не только для собеседований, но и для реальной разработки. Они помогают стать более сильным специалистом в реальности и в глазах работодателя. Какой вывод? Нужно прокачиваться в алгоритмах. Можно конечно начать изучать все самостоятельно, но зная многих людей — без кнута извне их сложно заставить самообучаться. Поэтому предлагаю тебе пойти на курс от Яндекс Практикума — "Алгоритмы и структуры данных". Хочешь писать эффективный код? Тебе туда. Хочешь успешно проходить собесы? Тебе туда. Хочешь накачать мышцу компьютер сайенса? Тебе туда. Хочешь знать, что за птица эта Дейкстра, где в коде можно встретить бор, и кто сделал кучу на дереве? Знаю, что хочешь. Тебе тоже туда. Если сомневаешься, пройди бесплатный вводный тест - на нем поймешь формат обучения и разберешься, что тебя будет ожидать на курсе. Все дороги идут туда. Так чего ждать? Реклама, АНО ДПО “Образовательные технологии Яндекса”, ИНН 7704282033, 119021, г. Москва, ул. Тимура Фрунзе, д. 11, корпус 2, erid: 2SDnjeQi4jV

Если бы у вас был 1'000'000$ на реализацию вашей айтишной бизнес идеи, то какой стартап бы вы создали?
Если бы у вас был 1'000'000$ на реализацию вашей айтишной бизнес идеи, то какой стартап бы вы создали?

​​Баг универсальной инициализации В С++11 нам завезли прекрасную фичу - автоматический вывод типов с помощью ключевого слова auto. Теперь мы можем не беспокоится по поводу выяснения типа итераторов для какой-нибудь мапы от мапы о вектора и написания этого типа. Вместо этого можно просто сделать вот так:
const auto it = map_of_map_of_vectors_by_string_key.find(value);
И тогда же ввели еще одну замечательную фичу - braced initialization. Она предотвращает парсинг вашей инициализации, как объявления функции, и убирает эффекты неявного приведения типов. Например, при компиляции такого кода
struct MyClass {
  int a = 0;
};

int main() {
  MyClass x();
  std::cout << x.a << std::endl;
}
компилятор выдаст что-то такое: warning: empty parentheses interpreted as a function declaration. Ну вот захотел я вызвать дефолтный конструктор явно. А мое определение трактуют как объявление функции(most vexing parse). Если круглые скобки заменить на фигурные, то все будет пучком. Однако эта универсальная инициализация, как ее называет Майерс, принесла с собой и несколько багов. Один из них мы уже обсуждали в этом посте. Но сегодня посмотрим еще на один. Вот хотим мы объявить целочисленную переменную и вдруг забыли как пишется int. Да, здесь мы сильно натягваем даже не сову, а прям воробья на глобус.
auto i{0};
А что, имеем право. auto ведь из инициализатора выводит тип, да?. Передали в качестве инициализатора целочисленный литерал. Какой тип будет у i? i будет иметь тип std::initializer_list<int>. Снова проблема именно в синтаксисе определения std::initializer_list и фигурными скобками. Такое поведение совсем контринтуитивное. Считается, что фигурноскобочная инициализация - более предпочитаемый паттерн для использования, но такие приколюхи сильно снижают универсальность этого паттерна. Да, в будущих стандартах эту "особенность" убрали, но не все могут пользоваться самими современными стандартами. Но хорошие новости, что, скорее всего, на современных версиях компиляторов при -std=c++11 вы не получите этого бага. Этот момент я объясню в следующем посте. Don't be confusing. Stay cool. #cpp11 #cppcore

​​Почему перегрузки не могут иметь разные возвращаемые значения #новичкам Обычно люди просто принимают на веру факт, что перегрузки различаются только набором аргументов. Впрочем, как и все, что вливается в голову в начале обучения. Но никогда не поздно задуматься над смыслом. А почему мы не можем перегружать функции по возвращаемому значению? Может вот так абстрактно рассуждая, и не сразу придет ответ. Но на самом деле все просто, если посмотреть на конкретный пример. Давайте представим, что никаких запретов нет. Теперь перегрузки могут различаться только возвращаемым значением. И у нас есть 2 функции, почти одинаковые , но имеют разные возвращаемые значения.
int GetRandomNumber();
float GetRandomNumber();
Пускай мы хотим генерировать и целые числа, и дробные числа. И то, и то число, общее название оправдано. Теперь попробуем вызвать первую версию.
int number = GetRandomNumber();
А вы уверены, что мы сейчас вызвали именно первую версию? Флоат спокойно кастуется к инту. Ничего не мешает компилятору выбрать вторую версию для этого вызова. Ситуация становится комичной, если использовать auto:
auto number = GetRandomNumber();
Тут как бы вообще нет ни намека на то, какую версию вызывать. Компилятор не умеет читать ваши мысли и намерения. Он не знает, какую версию вы хотели вызвать. Просто рандомно он выбрать не может. Появляется парадокс и вселенная схлопывается. Вот и получили противоречие. Компилятор просто не может отличить вызовы таких функций. Поэтому это и запрещено. Don't be confusing. Stay cool. #cppcore

❓ Знаете, что делает код на C++ мощным? Правильная работа с библиотеками! 👉 Если вы хотите стать востребованным C++ разработ
Знаете, что делает код на C++ мощным? Правильная работа с библиотеками! 👉 Если вы хотите стать востребованным C++ разработчиком, пора прокачивать навыки работы с библиотеками. Это ваш ключ к написанию профессионального и оптимизированного кода! Что вас ждет на открытом уроке: - узнаете, какие бывают библиотеки и чем они отличаются; - научитесь подключать сторонние библиотеки и собирать свои; - поймете, как библиотеки упрощают вашу работу и делают код эффективнее. ⭐️ Спикер Денис Злобин — старший инженер-программист в Astra Linux, опытный наставник разработчиков. ⏰ 18 декабря в 19:00 мск. Для начинающих и Junior-разработчиков, готовых к карьерному росту. Вебинар проходит в преддверии старта курса «C++ Developer», участники урока получат скидку на большое обучение. 👉 Для участия зарегистрируйтесь: https://otus.pw/uE2f/ Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru

std::visit #опытным Не так уж и просто работать с вариантными типами. Надо точно знать, какого типа объект находится внутри. Если не угадали - получили исключение. Ну или тестить объект на содержание в нем конкретного типа с помощью лапши из if-else. Так вот чтобы голова не болела при работе с std::variant надо 2 раза в день после еды принимать std::visit. Эта функция позволяет применять функтор к одному или нескольким объектам std::variant. И самое главное, что вам не нужно беспокоиться по поводу того, какой именно объект находится за личиной варианта. Компилятор все сам сделает.
template< class Visitor, class... Variants >  
constexpr visit( Visitor&& vis, Variants&&... vars );

template< class R, class Visitor, class... Variants >  
constexpr R visit( Visitor&& vis, Variants&&... vars );
Так выглядят ее сигнатуры. Первым параметром передаем функтор, дальше идут варианты. Попробуем использовать эту функцию:
using var_t = std::variant<int, long, double, std::string>;

std::vector<var_t> vec = {10, 15l, 1.5, "hello"};

for (auto& v: vec)
{
  var_t w = std::visit([](auto&& arg) -> var_t { return arg + arg; }, v);
  std::visit([](auto&& arg){ std::cout << arg; }, w);
}
//OUTPUT:
// 20 30 3 hellohello
Главное, чтобы функтор умел обрабатывать любую комбинацию типов, которую вы можете передать в него. Обратите внимание, что мы используем здесь generic лямбду, которая может принимать один аргумент любого типа. Если вы хотите передать в std::visit несколько объектов, то функтор должен принимать ровно такое же количество аргументов и уметь обрабатывать любую комбинацию типов, которая может содержаться в вариантах.
std::visit([](auto&&... arg){ ((std::cout << arg << " "), 
                               ..., 
                               (std::cout << std::endl)); }, vec[0], vec[1]);
std::visit([](auto&&... arg){ ((std::cout << arg << " "), 
                               ..., 
                               (std::cout << std::endl)); }, vec[0], vec[1], vec[2]);
// OUTPUT
// 10 15 
// 10 15 1.5
Используем здесь дженерик вариадик лямбду, чтобы она могла принимать столько аргументов, сколько нам нужно. И эта конструкция работает для любого количества переданных объектов std::variant; Так что std::variant и std::visit - закадычные друзья и им друг без друга грустно! Не заставляйте их грустить. Have a trustworthy helper. Stay cool. #template #cpp17

Подробно рассказываем о высокопроизводительных решениях с фреймворком userver на новом бесплатном вебинаре! Он предназначен д
Подробно рассказываем о высокопроизводительных решениях с фреймворком userver на новом бесплатном вебинаре! Он предназначен для создания надежных и молниеносных микросервисов и веб-серверов. Освойте передовой C++ фреймворк всего за 90 минут!  Вы узнаете: - Уникальные архитектурные особенности и преимущества userver, которые сделают вашу работу легче и эффективнее. - Как быстро настроить окружение и запустить свой первый проект, не тратя лишнего времени. - Советы высококлассных профессионалов по работе с асинхронностью, которые помогут вам стать мастером своего дела и очень много другой полезной сочной информации! 💡 Будет особенно интересно: - C++ разработчикам, стремящимся освоить мощный инструмент для создания микросервисов. - Backend-инженерам и Team Lead'ам, ищущим высокопроизводительные решения для своих задач. - Специалистам, работающим над масштабируемыми системами. ❌❌Получите практический и ценный опыт работы с userver, который можно сразу применить в своих проектах - регистрируйтесь по ссылке: https://otus.pw/Ocpbe/ P.S. Все зарегистрированные участники получат приличную скидку на обновленный топовый курс «C++ Developer. Professional» Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576

Получаем адрес перегрузки #новичкам Представьте, что у вас есть функция, которая вызывает любой коллбэк:
template<class T, class... Args>
void call_callback(T callback, Args... args) {
    callback(args...);
}
И есть другая функция, которую вы вызываете через call_callback.
int func() {
    return 42;
}

call_callback(func);
Все работает прекрасно. Но теперь мы добавляем перегрузку func и пытаемся их вызвать через call_callback.
int func() {
    return 42;
}

int func(int num) {
    return num;
}

call_callback(func);
call_callback(func, 42);
Получаем ошибку
error: no matching function for call to
 ‘call_callback(<unresolved overloaded function type>)
Компилятор не может понять, какую конкретно перегрузку вы имели ввиду. Дело в том, что имя функции неявно приводится к указателю на эту функцию. Тип указателя на функцию зависит от ее сигнатуры. И просто по имени невозможно понять, с какой сигнатурой функцию мы имеем ввиду. Что же делать? Дать компилятору подсказку и использовать static_cast.
call_callback(static_cast<int(*)()>(func));
call_callback(static_cast<int(*)(int)>(func), 42);
Теперь все работает, как надо. Give useful hints. Stay cool. #cppcore

Получаем адрес стандартной функции Иногда людям приходится работать с указателями на функции. И у них может возникнуть надобность взять адрес у стдшной функции. Например, вы хотите как-то трансформировать каждый символ в строке c помощью std::transform и в качестве коллбэка передаете именно указатель на функцию
std::transform(s.begin(), s.end(), s.begin(), std::toupper);
Вроде ничего страшного не должно случиться. Но согласно стандарту, поведение программы в этом случае unspecified и потенциально даже ill-formed. Все потому что нельзя брать адреса у стандартных функций. Начиная с С++20 это явно прописано в стандарте.
Let F denote a standard library function, a standard 
library static member function, or an instantiation of 
a standard library function template.

Unless F is designated an addressable function, 
the behavior of a C++ program is unspecified 
(possibly ill-formed) if it explicitly or implicitly 
attempts to form a pointer to F.

Possible means of forming such pointers include 
application of the unary & operator, addressof, 
or a function-to-pointer standard conversion.

Moreover, the behavior of a C++ program is 
unspecified (possibly ill-formed) if it attempts 
to form a reference to F or if it attempts to form 
a pointer-to-member designating either a standard library 
non-static member function  or an instantiation of
a standard library member function template.
Нельзя формировать указатели и ссылки на стандартные функции и нестатические методы. Но почему? Чем они отличаются от обычных функций? На самом деле ничем. Дело в том, что будет с функциями в будущем. Вы без проблем получите адрес одиночной функции. Но если вы добавите к ней перегрузку, то у вас все резко перестанет компилироваться, потому что компилятор не сможет понять, какую конкретно вы используете перегрузку. А стандарт - это штука не статичная. В него постоянно добавляются новые фичи и обновляются в том числе старые инструменты. Например, с приходом С++11 у нас появилась мув-семантика. И условный метод вектора push_back обзавелся новой перегрузкой для правой ссылки. И это сломало код, который брал адрес метода push_back.
#include <vector>

template<typename T>
void Invoke(std::vector<int>& vec, T mem_fun_ptr, int arg)
{
  (vec.*mem_fun_ptr)(arg);
}

int main()
{
  std::vector<int> vec;
  Invoke(vec, &std::vector<int>::push_back, 42);
}
Этот код успешно собирается на 98 плюсах, но не может этого сделать на 11-м стандарте. Можете поиграться с примером на годболте. Если вам все равно нужно сделать что-то подобное, то оберните вызов стандартной функции в лямбду. Тогда вы ничего не нарушите.
std::transform(s.begin(), 
               s.end(), 
               s.begin(), 
               [](unsigned char c){ return std::toupper(c); });
Don't break your future. Stay cool. #cppcore #cpp20

⚡XMAS HACK к нам мчится! С 20 по 23 декабря под звон Jingle bells пройдет самый яркий и праздничный хакатон 2024 года - XMAS
⚡XMAS HACK к нам мчится!  С 20 по 23 декабря под звон Jingle bells пройдет самый яркий и праздничный хакатон 2024 года - XMAS HACK.  🎁Дед Мороз и Снегурочка уже положили под елочку XMAS HACK особый подарок - 1 000 000 рублей!  ✨Уникальный кейс от Интерюнис-ИТ: Реновация пользовательского интерфейса программного обеспечения акустико-эмиссионного измерительного комплекса.  👨‍💻Задача: Улучшить текущий пользовательский интерфейс программы без модификации содержательной части кода. Основной фокус модификации визуальных элементов, навигации и общем удобстве использования, сохраняя при этом все существующие функциональные возможности. 🚀Осталось собрать команду и до 19 декабря подать заявку на участие: https://tglink.io/642c1efadcff?erid=LjN8KCi28 🎅XMAS HACK к нам мчится, скоро все случится!🌟 #реклама О рекламодателе

​​std::jthread С std::thread в С++ есть один интересный и возможно назойливый нюанс. Давайте посмотрим на код:
int main()
{
  std::thread thr{[]{ std::cout << "Hello, World!" << std::endl;}};
}
Простой Хелло Ворлд в другом потоке. Но при запуске программы она тут же завершится примерно с таким сообщением: terminate called without an active exception. Эм. "Я же просто хотел быть счастливым вывести в другом потоке сообщение. Неужели просто так не работает?" В плюсах много чего просто так не работает) А вот такая программа:
int main()
{
  std::thread thr{[]{ std::cout << "Hello, World!" << std::endl;}};
  std::this_thread::sleep_for(std::chrono::seconds(1));
}
Все-таки напечатает Hello, World!, но потом все равно завершится с std::terminate. Уже лучше, но осадочек остался. Ничего не понятно. Давайте разбираться. С помощью слипа мы немного затормозили main тред и сообщение появилось. То есть мы оттянули выход из main и завершение программы и дали возможность подольше поработать новосозданному потоку. А что происходит при выходе из скоупа функции? Вызов деструкторов локальных объектов. Так вот в деструкторе единственного локального объекта и проблема. Согласно документации, для каждого потока мы обязательно должны выбрать одну из 2-х стратегий поведения: отсоединить его от родительского потока или дождаться его завершения. Делается это методами detach и join соответственно. И если мы не вызовем один из этих методов для объекта потока, то в своем деструкторе он вызовет std::terminate. То есть корректный код выглядит так:
int main()
{
  std::thread thr{[]{ std::cout << "Hello, World!" << std::endl;}};
  thr.join();
}
Мы дожидаемся конца исполнения потока и только после этого завершаем программу. Теперь никаких терминаторов. Но зачем эти формальности? Вообще говоря, часто мы хотим присоединить поток почти сразу перед вызовом его деструктора. А вот отсоединяем поток мы почти сразу после создания объекта. Мы же заранее знаем, хотим ли мы отпустить поток в свободное плавание или нет. И, учитывая эти факты, было бы приятно иметь возможность не вызывать join самостоятельно, а чтобы он за нас вызывался в деструкторе. И С++20 приходит здесь нам на помощь с помощью std::jthread. Он делает ровно это. Если его не освободили и не присоединили мануально, то он присоединяется в деструкторе. Поэтому такой код сделает то, что мы ожидаем:
int main()
{
  std::jthread thr{[]{ std::cout << "Hello, World!" << std::endl;}};
}
jthread не только этим хорош. Его исполнение можно еще отменять/приостанавливать. Но об этом уже в другой раз. Кстати. Вопрос на засыпку. Слышал, что там какие-то сложности у кланга с jthread были. Сейчас все нормально работает? Make life easier. Stay cool. #cpp20 #concurrency

Дефолтные и ссылочные параметры Есть такой паттерн или стиль написания кода, как входные+выходные параметры у функции. Вместо того, чтобы возвращать результат работы функции в возвращаемом значении, его возвращают через ссылочные параметры. И вот допустим, у нас программист Иннокентий. У него в проекте есть функция, которая хорошо и без нареканий работает и отправляет запрос базе
bool DBConnection::SendQuery(const char* query, 
                             const DbQueryParams& params = DbQueryParams{});
Если запрос отослан удачно, возвращаем тру, если нет, то фолс. Строка запроса к базе может содержать плейсхолдеры(это метки в подготовленном запросе, куда будут подставляться актуальные значения параметров), а может и не содержать. Если они есть, то вместе с query передаются и параметры для подстановки в запрос. Если нет, то у нас есть пустое значение по-умолчанию. Но жизнь течет, все изменяется. Кенни поступила задачка проверять состояние конекшена перед отправкой запроса, возвращать результат проверки наружу и ...(построить какую-то логику на результате). А в проекте у него используется паттерн с выходными параметрами. Обычно это реализовывается так: в начале в аргументах функции идут все входные параметры и затем все выходные. Вроде как логично. Он, согласно своему код стайлу, пишет:
bool DBConnection::SendQuery(const char* query, 
                             const DbQueryParams& params = DbQueryParams{}, 
                             DbConnectionState& state);
Реализовал функцию и запустил билд. А он, хромоногий, упал. Нельзя указывать параметры функции без значения по умолчанию после параметров, имеющих это значение. Видимо это сделано так, потому что иначе появляется пространство для неопределенности. Ладно еще в этом случае компилятор видит разные типы и может как-то соотнести 2 и 2. Но например в случае тривиальных типов все не так однозначно. Они могут неявно конвертироваться друг в друга и тут уже распарсить нельзя.
void foo(int i, float j = 5.0, unsigned k);

foo(1, 2); // Невозможно понять, вызвали ли вы foo(1, 5.0, 2) или 
например по ошибке передали слишком мало аргументов
Закатив глаза и особо не думая, он исправляет:
bool DBConnection::SendQuery(const char* query, 
                             const DbQueryParams& params = DbQueryParams{}, 
                             DbConnectionState& state = DbConnectionState::OK);
И билд тоже падает! Но теперь уже по другому поводу. Неконстантные левоссылочные параметры нельзя определять со значениями по-умолчанию. Причина тут очень простая. Вы не можете создать левую ссылку из значения типа rvalue reference. У объекта должно быть имя, чтобы его можно было присвоить неконстантной левой ссылке. У DbConnectionState::OK имени нет, поэтому и упали. Выход только один. Нарушать свой код-стайл. Придется пихать параметр DbConnectionState& state либо первым параметром, либо между query и params. Первый способ вообще в принципе нарушает все негласные соглашения в объявлениях функции среди всех языков. Второй вроде подходит, когда мы не передаем params. Но в случае, если в запросе есть плейсхолдеры и нам приходится передавать params, то выглядит все не менее ужасно. Кенни не придумал ничего лучше и сильно расстроился. Пошел в лес, увидел там машину, сел в нее и сгорел. А что Иннокентий в принципе мог сделать в этой ситуации? Жду ваших вариантов в комментариях) Don't worry. All difficulties will pass. #cppcore

🛠Приглашаем на открытый вебинар: Автоматическая регулировка усиления каскадов на операционных усилителях и транзисторах Вы у
🛠Приглашаем на открытый вебинар: Автоматическая регулировка усиления каскадов на операционных усилителях и транзисторах Вы узнаете теоретические и практические аспекты реализации автоматической регулировки усиления каскадов, построенных на операционных усилителях и транзисторах, работающих в частотном диапазоне до 10 МГц. Вы обучитесь подходам к изменению коэффициентов усиления и ограничению мощности входного сигнала. На практике попробуете проектировать каскады с АРУ, использовать SPICE симулятор KiCad и добавлять модели компонентов для анализа. Всё это — в интерактивном формате с реальными примерами. Присоединяйтесь! 🔥Регистрируйтесь на урок 17 декабря, в 20:00 мск и получите скидку на большое обучение «Электроника и электротехника»: https://otus.pw/SkiK/ Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576

Подкасты Если вы думали, что подкаст - это такая штука, которая нужна только, чтобы под нее ехать в транспорте, кушать или тренироваться, то у меня для вас хорошие новости. Подкасты еще могут помогать образовываться! Основная проблема подкастов - чтобы поддерживать адекватный уровень вовлеченности должна быть или интересная личность или просто нескончаемый поток годной и полезной информации. Кажется, есть кандидат, который сочетает в себе и то, и другое. Еще и на смежную с плюсами тему! У компании Ядро(неподтвержденная информация) есть свой подкаст "Битовые маски", где специалисты высочайшего класса обсуждают низкоуровневое программирование, процессоры, компиляторы и операционные системы. Порог входа там довольно большой, но именно благодаря таким материалам вы можете серьезно повысить по крайней мере свою осведомленность о том, что происходит под капотом привычных нам инструментов. У ребят уже 18 выпусков, так что контента вам хватит за глаза. Если что это не реклама. Ребята реально делают качественный контент, поэтому этот пост для всех тех, кто по каким-то причинам пропустил битовые маски мимо своего информационного поля. Вот ссылочка на плейлист с выпусками: https://www.youtube.com/watch?v=wknD9AGvKdc&list=PL0YYm7t_DM63uOt3OF2qRpB5rL27aceLs Enjoy education. Stay cool.