Грокаем C++ – Telegram
Грокаем C++
9.37K subscribers
45 photos
1 video
3 files
577 links
Два сеньора C++ - Владимир и Денис - отныне ваши гиды в этом дремучем мире плюсов.

По всем вопросам (+ реклама) @ninjatelegramm

Менеджер: @Spiral_Yuri
Реклама: https://telega.in/c/grokaemcpp
Мы на TGstat: https://tgstat.ru/channel/@grokaemcpp/stat
Download Telegram
​​Оборачиваем вспять байты
#новичкам

Когда мы низкоуровнево работаем с сетью, то надо понимать, что в данных, полученных по сети, нужно реверсировать порядок байтов, чтобы правильно интерпретировать значения. Также реверсировать порядок нужно при отправке данных по сети. Это происходит из-за того, что в стеке протоколов TCP/IP принят порядок Big-endian - старший байт хранится по младшему адресу. А на большинстве хостов(десктопов и серверов) - Little-endian: младший байт хранится по младшему адресу.

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

### GCC/Clang

uint16_t swapped16 = __builtin_bswap16(value);
uint32_t swapped32 = __builtin_bswap32(value);
uint64_t swapped64 = __builtin_bswap64(value);

### MSVC:

uint16_t swapped16 = _byteswap_ushort(value);
uint32_t swapped32 = _byteswap_ulong(value);
uint64_t swapped64 = _byteswap_uint64(value);


Либо системное апи:

#include <arpa/inet.h>  // Linux/macOS
// или
#include <winsock2.h> // Windows

uint16_t network_to_host16 = ntohs(value);
uint16_t host_to_network16 = htons(value);

uint32_t network_to_host32 = ntohl(value);
uint32_t host_to_network32 = htonl(value);

uint64_t network_to_host64 = ntohll(value);
uint64_t host_to_network64 = htonll(value);


Либо какое-нибудь библиотечное решение:

#include <boost/endian/conversion.hpp>

uint32_t value = 0x12345678;
uint32_t swapped = boost::endian::endian_reverse(value);

uint32_t to_big = boost::endian::native_to_big(value);
uint32_t to_little = boost::endian::native_to_little(value);


Но в С++23 появилась стандартная функция для разворачивания порядка байтов!

template< class T >
constexpr T byteswap( T n ) noexcept;


Работает она только для интегральных типов и вот ее возможная реализация:

template<std::integral T>
constexpr T byteswap(T value) noexcept
{
static_assert(std::has_unique_object_representations_v<T>,
"T may not have padding bits");
auto value_representation = std::bit_cast<std::array<std::byte, sizeof(T)>>(value);
std::ranges::reverse(value_representation);
return std::bit_cast<T>(value_representation);
}


Результат у нее собственно ровно тот, который и ожидается:

template<std::integral T>
void dump(T v, char term = '\n')
{
std::cout << std::hex << std::uppercase << std::setfill('0')
<< std::setw(sizeof(T) * 2) << v << " : ";
for (std::size_t i{}; i != sizeof(T); ++i, v >>= 8)
std::cout << std::setw(2) << static_cast<unsigned>(T(0xFF) & v) << ' ';
std::cout << std::dec << term;
}

int main()
{
static_assert(std::byteswap('a') == 'a');

std::cout << "byteswap for U16:\n";
constexpr auto x = std::uint16_t(0xCAFE);
dump(x);
dump(std::byteswap(x));

std::cout << "\nbyteswap for U32:\n";
constexpr auto y = std::uint32_t(0xDEADBEEFu);
dump(y);
dump(std::byteswap(y));

std::cout << "\nbyteswap for U64:\n";
constexpr auto z = std::uint64_t{0x0123456789ABCDEFull};
dump(z);
dump(std::byteswap(z));
}

// OUTPUT
// byteswap for U16:
// CAFE : FE CA
// FECA : CA FE

// byteswap for U32:
// DEADBEEF : EF BE AD DE
// EFBEADDE : DE AD BE EF

// byteswap for U64:
// 0123456789ABCDEF : EF CD AB 89 67 45 23 01
// EFCDAB8967452301 : 01 23 45 67 89 AB CD EF


Как всегда стандарт запаздывает лет на 10-15-20, но хорошо, что все-таки завезли эту полезную функцию, которую можно кроссплатформенно использовать.

Use standard solutions. Stay cool.

#cpp23
28👍12😁8🔥5
И это все компилируется!
Сможете сказать, откуда каждая скобка взялась?)
1🤯41😁109👀5🔥3🎄3🥱1
​​Атрибуты лямбды
#опытным

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

В С++11 у нас появилась возможность указывать атрибуты для функции. Например:

[[nodiscard]] int ComplicatedCompute() {
return 2*2;
}

ComplicatedCompute();
// warning: ignoring return value of 'int ComplicatedCompute()',
// declared with attribute nodiscard


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

Ну это функции. А как же лямбды? Хочется и для них указывать атрибуты.

И атрибуты для возвращаемого значения лямбды завезли в С++23. Выглядит это так:

auto complicated_compute = [] [[nodiscard]] () { return 2 * 2; };

complicated_compute();
// warning: ignoring return value of 'main()::<lambda()>',
// declared with attribute 'nodiscard'


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

Одни скажут: "усложнение синтаксиса!". Другие скажут, что давно пора лямбды подтягивать ко всем возможностям обычных функций.

Тут как бы все просто: не хотите - не используйте. У лямбды и так полно опциональных обвесок, одним больше, одним меньше. Можно определить шаблонную лямбду и обвесить ее всякими концептами с trailing return type. И это будет страшный зверь. Можно сделать отдельный пост, как может выглядеть ультимативная лямбда.

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

Don't ignore. Stay cool.

#cpp23
21👍9🔥8😁3
Атрибуты везде
#опытным

Используют атрибуты функций не только лишь все, мало кто знает, куда их можно пихать.

Есть на самом деле 3 легальных места для навешивания атрибутов на функцию.

1️⃣ Перед типом возвращаемого значения:

[[deprecated]] int foo() { return 42; }


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

foo();
// warning: 'int foo()' is deprecated


2️⃣ После имени функции:

int foo [[deprecated]] () { return 42; }


В таком виде атрибут тоже применяется к самой функции.

3️⃣ После параметров:

int foo() [[gnu::deprecated]] { return 42; }


Тогда атрибут применяется к типу функции, а не к самой функции. Разница вот в чем:

int foo() [[gnu::deprecated]] { return 42; }

int main() {
foo(); // no warnings
using FuncType = decltype(foo); // use of type is deprecated
}


Обычный вызов функции прекрасно компилируется. Но вот использование типа функции через decltype помечается как устаревшее.

Причем gcc и clang по-разному интерпретируют эту ситуацию. Clang говорит, что gnu::deprecated нельзя применять к типам и игнорирует атрибут. Вот ссылка на годболт для интересующихся.

Соответственно, в лямбде в тех же местах можно ставить атрибуты:

auto complicated_compute = [] [[nodiscard]] () [[gnu::deprecated]] {
return 2 * 2;
};


Признавайтесь, знали?)

Have your own opinion. Stay cool.

#cppcore
👍23🤯199🔥6
​​Атрибуты параметров функции
#новичкам

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

class Interface {
public:
virtual void method(int param) = 0;
};

class Implementation : public Interface {
public:
void method(int param) override {
// this implementation doesn't use param so mark it
}
};


Вы определили какой-то интерфейс с методом, принимающим один параметр. И в какой-то момент появилась необходимость создать наследника, реализующего этот интерфейс, однако реализации не нужен параметр param. Возможно Implementation - это какой-то мок, у которого в принципе пустая реализация.

Если вы активно используете варнинги компилятора и прочие линтеры, при попытке собрать такой код вы скорее всего увидите предупреждение/ошибку компиляции. Чтобы стало все чётенько, стоит пометить param атрибутом maybe_unused, тем самым явно указав компилятору, что параметр не используется намеренно. И проблема исчезнет.

Однако из стандартных атрибутов по сути имеет смысл использовать только этот самый maybe_unused.

Но атрибуты - это не только средство общения с компилятором. Это еще и средство налаживания коммуникации между автором кода и его пользователями/читателями.

Например:

size_t safe_strcpy(
[[gnu::nonnull]] char* dest,
[[gnu::nonnull]] const char* src,
size_t dest_size
);


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

Annotate your code. Stay cool.

#cppcore
21🔥14👍8🤯1
​​Множество атрибутов
#опытным

Если вы хотите указать несколько атрибутов для вашей функции, вы можете использовать следующий синтаксис:

1️⃣ Списочный. Внутри одних скобок перечисляете все атрибуты:

[[gnu::always_inline, gnu::const, gnu::hot, nodiscard]] int f();


2️⃣ Многоскобочный. Для больших любителей распиленных квадратов. Очень больших:

[[gnu::always_inline]] [[gnu::hot]] [[gnu::const]] [[nodiscard]] int f();


Больше квадратных скобок!

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

[[using gnu : always_inline, const, hot]] [[nodiscard]] int f();


Но тогда котлеты отдельно, мухи отдельно. Все атрибуты одного неймспейса нужно уносить в отдельные скобки. Это фича С++17.

Что интересно, вы можете написать полную чупуху:

[[rust, will, replace, cpp]] int f();


И это скомпилируется! Стандарт поддерживает любые implementation-defined атрибуты. Причем неизвестные атрибуты просто игнорируются. Правда игнор спровождается варнингами, которые тем не менее можно скрыть опциями, подобным -Wno-attributes.

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

Love squares. Stay cool.

#cppcore #cpp17
🔥2112👍9👎1
Яндекс приглашает на встречу РГ21 С++ 15 декабря в Москве

Собираемся сообществом экспертов и энтузиастов С++, чтобы обсудить развитие стандарта, участие российских разработчиков в нем, а еще — внезапные новинки.

В центре встречи — выступление Антона Полухина, руководителя группы разработки общих компонентов в Яндексе. Он поделится новостями с последней встречи международного Комитета по стандартизации, расскажет о прогрессе в работе над С++26 и ответит на вопросы о том, как российским разработчикам участвовать в развитии стандарта языка.

Когда: 15 декабря, 18:30
Где: Москва, офлайн + онлайн-трансляция

Регистрация на встречу
🔥93👍3
Парсим ужас
#новичкам

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

[[]][[]]int main()[[]]{{[][][[]][[]]{{{}}}(main);}}


Если включить clang-format, то код преобразится во что-то такое:

/*1*/[[]][[]] int main()/*2*/[[]] {
{
[]/*3*/[[]](/*4*/[[]] /*5*/auto [])/*6*/[[]][[]] {
{
{}
}
}/*7*/(main);
}
}


Давайте посмотрим, откуда так много скобок:

1️⃣ Перед типом возвращаемого значения main определены 2 пустые области для указания атрибутов функции main.

2️⃣ Перед телом функции main определена пустая область для атрибутов, применяемых к типу функции main.

3️⃣ После блока захвата лямбды определена пустая область для атрибутов, применяемых к самой лямбде.

4️⃣ Внутри списка параметров лямбды определена пустая область для атрибутов, применяемых к единственному параметру лямбды.

5️⃣ Сама лямбда является generic и принимает массив неизвестного типа.

6️⃣ Перед телом лямбды определены 2 пустые области для атрибутов, применяемых к типу лямбды.

7️⃣ Вызываем лямбду с помощью указателя на функцию main.

8️⃣ Ну и разбавили это дело несколькими лишними скоупами по пути.

Не так уж и сложно оказалось)

Так, новичковая часть закончилась.
#опытным

Интересно, что этот код компилируется на gcc, но не на clang.

cppinsights показывает, что лямбда раскрывается во что-то такое:

class __lambda_5_17 {
public:
template <class type_parameter_0_0>
inline /*constexpr */ auto operator()(auto *) const {
{ {}; };
}

private:
template <class type_parameter_0_0>
static inline /*constexpr */ auto __invoke(auto *__param0) {
return __lambda_5_17{}.operator()<type_parameter_0_0>(__param0);
}

public:
// /*constexpr */ __lambda_5_17() = default;
};


То есть по факту мы имеем шаблонный оператор с auto параметром.

Как интерпретировать эту штуку - дело нетривиальное и по ходу компиляторы это делают по-разному. Видимо gcc при попытке инстанцировать шаблон с параметром int() выводит auto как тот же самый тип функции int() и в итоге лямбда принимает указатель на функцию. А clang при попытке инстанцировать шаблон выводит тип параметра функции как массив функций int() и не может принять main в качестве такого параметра.

Пишите ваше мнение, кто прав, кто виноват)

Deal with horrible things step by step. Stay cool.

#cppcore #compiler
❤‍🔥21😁128👍3🔥2
​​Мок собеседования
#новичкам #опытным

Представьте, что вы вкатун в АйТишку плюсовую. Прочитали несколько книжек, прошли кучу бесплатных курсов и дописали свой первый велосипед пет-проект.

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

Но как узнать, как проходить собесы, если никогда их не проходил?(оставим за скобками вопрос, как вообще добраться до собеса, это то еще шаманство)

Для этого существуют мок-собеседования. То есть дословно "имитация" собеседования.

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

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

В сети лежит куча готовых мок-собеседований по С++ на позиции разных уровней.

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

Даже в русскоязычном пространстве можно найти много таких видосов. Основные мок-интервьюеры у нас это:

- Ambushed raccoon
- Владимир Балун

Ну а мы за вас собрали подборку всех(или почти всех) мок-собесов на русском языке по С++ и разбили их по уровням.

Junior:

- https://www.youtube.com/watch?v=_-EkLLZ5svk
- https://www.youtube.com/watch?v=H1mIHJxnm9E
- https://www.youtube.com/watch?v=PQ1C_0EAHFI
- https://www.youtube.com/watch?v=BCpHj698D8U
- https://youtu.be/7g8HufwNa0g?si=XVKRsuoHN20MJx3i
- https://youtu.be/a18qTcWn-II?si=OttfqKh0bHLjueOY
- https://www.youtube.com/live/rLOgkn6xVQA?si=lFqwGf_Wsr8IohHo
- https://youtu.be/VfoxaNLVtmQ?si=elt-OyZWB5tXp5hI


Middle:

- https://www.youtube.com/watch?v=nMdNehH8-Ss
- https://www.youtube.com/watch?v=Ed37R0FvkQ8
- https://www.youtube.com/watch?v=IDqMy4_xkb4
- https://www.youtube.com/watch?v=BOUEbS5L4-8
- https://www.youtube.com/watch?v=PwVMcxCBIkg
- https://www.youtube.com/watch?v=Np6UrKN6ZbA
- https://www.youtube.com/watch?v=1Ez3kbK_3bI
- https://www.youtube.com/watch?v=s6BXbEPaw5g
- https://www.youtube.com/watch?v=yfoFtu28n4o
- https://www.youtube.com/watch?v=cT3fonCyxJk
- https://www.youtube.com/watch?v=xwb2FAKxCUo
- https://www.youtube.com/watch?v=bOgz4K-ARzQ
- https://www.youtube.com/watch?v=wR4VRCp_BYo
- https://youtu.be/5enBKMwOST0?si=i-5mdyrxeeFiZKo1


Senior:

- https://www.youtube.com/watch?v=OwMEK_W8Ysw
- https://www.youtube.com/watch?v=dZpe58HKX-8

Просто вопросы и задачи с собесов:

- https://www.youtube.com/watch?v=boYk6gFg84E
- https://www.youtube.com/watch?v=ViHNB0_1j90
- https://www.youtube.com/watch?v=aYM7lksQ8yg
- https://www.youtube.com/watch?v=UdY_YMFx7SY
- https://www.youtube.com/watch?v=PStQ4jhhz08
- https://www.youtube.com/watch?v=wMYfg_iPqMQ


Просмотрев эти видосы(возможно по нескольку раз) и переписав ответы на все вопросы себе в тетрадочку или файлик, вы будете знать ответы на 95% устных вопросов, которые вам будут задавать в условной компании Х.

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

Ну а для любителем native english есть канал Кодингового Иисуса. Он постоянно у себя на стримах спрашивает у людей за плюсы. В основном люди валятся на простых вопросах, но иногда попадаются качественные собеседники. По крайней мере практика языка вам будет точно обеспечена.

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

Practice makes perfect. Stay cool.

#interview
53🔥17👍9❤‍🔥4🙏4
🎯 В C++ вызываемый объект — это намного больше, чем “функция”. За тридцать лет язык прошёл путь от простых указателей до мощных лямбда-выражений, которые лежат в основе современного параллелизма, асинхронности, алгоритмов и гибких архитектур.

На открытом уроке 18 декабря в 20:00 мск вы увидите эволюцию callable-подходов во всей широте: от function pointers и функторов до std::bind, std::function и современных лямбд. Разберёмся, что происходит «под капотом», как выбирать подходящий инструмент и почему именно лямбды стали стандартом промышленного C++.

Такой разбор особенно полезен тем, кто хочет писать более выразимый, модульный и безопасный код. Понимание callable-моделей даёт гибкость: проще проектировать API, реализовывать callback-механику, строить асинхронность, оптимизировать архитектуру приложения и мыслить “по-современному”.

⚡️Присоединяйтесь к открытому уроку в преддверие старта курса «C++ Developer» — получите базу, на которой строятся потоки исполнения, асинхронные модели и ключевые элементы современного C++20/23: https://otus.pw/cY5r/

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
4👍3🔥3🥱3
Как запустить cpp файл из консоли?

Да, да, именно запустить файл. Берете bash, берете файл. Как одно воткнуть в другое и получить результат работы программы?

Следите за пальцами:

#if 0
g++ test.cpp -o test && ./test
exit 0
#endif

#include <iostream>

int main() {
std::cout << "Hello, World!" << std::endl;
}


Есть ли мы попытаемся запустить файл с этим содержимым через терминал, то будет выполняться следующая последовательность шагов:

1️⃣ Ядро смотрит на первые 2 байта файла и пытается найти там шебанг - #!. Если нашла, то в этой строчке будет указан путь до нужного интерпретатора.

2️⃣ Если шебанга нет и файл не является исполняемым(как у нас), то файл считается shell-скриптом и исполняется с помощью текущего командного интерпретатора.

3️⃣ Для shell-скриптов символ # обозначает начало однострочного комментария, поэтому первая строчка игнорируется.

4️⃣ Интерпретатор встречает команду компиляции и выполняет ее.

5️⃣ Теперь мы пытаемся реально скомпилировать этот файл с помощью g++. На этапе препроцессинга ветка условия #if 0 выбросится из текста файла и компилироваться будет только реально С++ код.

6️⃣ После компиляции интерпретатор запускает исполняемый файл и завершает работу на инструкции exit 0. Остальной С++ код он уже не увидит.

Напишите в терминале

chmod +x test.cpp
./test.cpp


И увидите заветные слова.

Дожили! Превратили С++ в питон...

PS: Большое спасибо за идею и материалы Даниилу @dkay7.

Have a fun. Stay cool.

#fun
50🔥17👍16😁12🤯11👀2🗿2🐳1
​​Bootstrap
#новичкам

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

Также возможно вы слышали, что компилятор java написан на java.

Если вдуматься в эти факты, то невольно задаешься вопросом: а как это вообще возможно? Нельзя же себя поднять за волосы из болота.

Вообще-то барон Мюнхгаузен смог и мы сможем!

Нужно лишь немного опоры. Чуть-чуть оттолкнуться.

Давайте пройдем весь процесс от и до.

1️⃣ Вы решили написать свой язык самый лучший язык во всей вселенной GOAT. Разработали синтаксис и правила языка, пару раз пооргазмировали от его крутости.

2️⃣ Дальше вам нужно уметь конвертировать код на новом языке в программу. Выхода нет: вы быстренько пишите простой компилятор или интерпретатор GOAT на уже существующем языке. Пусть для определенности вы написали компилятор на С. Компилируете его каким-нибудь gcc и у вас появляется первый компилятор языка GOAT. Это так называемый компилятор начальной загрузки или bootstrap компилятор.
Отлично! Вы можете писать программы на новом языке!

3️⃣ Но компилятор - это же тоже программа. Теперь вы можете на GOAT написать компилятор GOAT и скомпилировать его bootstrap компилятором. В итоге у вас получится рабочий компилятор GOAT, написанный на языке GOAT! Вот яйцо и родило яйцо.

4️⃣ В дальнейшем вы можете развивать компилятор, используя сам язык GOAT и перекомпилируя его самим собой.

Весь этот процесс называется bootstrapping.

Первый компилятор и jvm были написаны на С. Первый компилятор С был написан на ассемблере. Pascal'я - на Fortran.

Интересно, что clang изначально был написан на урезанной версии С++, которая компилировалась и на gcc, и на msvc. Поэтому clang можно использовать на винде.

Преимущества бутстраппинга:

👉🏿 Демонстрирует зрелость и самодостаточность языка. Можно развиваться самостоятельно и не зависеть от других технологий.

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

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

👉🏿 Упрощается разработка компилятора. Не нужно знать несколько языков.

А вот как бутстрапился С++ разберем в следующем посте.

Be self-sufficient. Stay cool.

#tools #compiler
25👍13🔥6🤯1