tgindex

C++: Хроники Дурки🚑

описание

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

909
подписчиков
Охват к подписчикам
247,3%
ERR
Реакции к просмотрам
1,61%
725 на 20 постов
Пересылки к просмотрам
0,68%
304
Постов в день
0,0
всего 20

Где отзываются чаще

доля реакций к просмотрам
  • 10 авг.Ладно, разумеется прошлый пост был набросом. Никогда не обновляйте компиляторы без очень серьезных на то причин. Мало ли что там сломается, а у вас не будет команды все это чинить. Вот к примеру. Вложенные лямбды с параметр паком. #include <utility> template <std::size_t N, std::size_t M> concept C = requires { []<std::size_t... Is>(std::index_sequence<Is...>) requires requires { []<std::size_t... Js>(std::index_sequence<Js...>) requires requires { true; } {}(std::make_index_sequence<M>{}); } {}(std::make_index_sequence<N>{}); }; static_assert(C<2, 3>); В clang 17 они нормально компилируются. В clang 20 они крашат компилятор. В clang 22 это починили и они компилируются. В текущем транке снова ломают компилятор. Не выходи из комнаты, не совершай ошибку. Компилятор обновляют слабаки и трусы.5,13%
  • 3 июл.Ой, я когда-нибудь брошу этот сраный язык. Давайте разберем по шагам. int main() { Box b; b->hi(); } Что тут происходит? Мы создаем объект типа Box, иииии... вызываем у него оператор ->, а у того, что он вернет вызывает функцию hi(). Логично? Логично! Смотрим, что такое у нас тип Box. struct Box { Proxy operator->() { return {}; } }; Ага, значит оператор выдает экземпляр объекта типа Proxy, и вот у него вызывается функция hi(). Логично? Логично. Смотрим, что такое структура Proxy. struct Proxy { Leaf* operator->() { static Leaf x; return &x; } }; В структуре Proxy функции hi() нет. Ошибка компиляции. Логично? А хрен там плавал. Уберите детей от мониторов. Вот этот код компилируется и выводит ok #include <iostream> struct Leaf { void hi() { std::cout << "ok"; } }; struct Proxy { Leaf* operator->() { static Leaf x; return &x; } }; struct Box { Proxy operator->() { return {}; } }; int main() { Box b; b->hi(); } Итак. У нас С++ разворачивает оператор -> до самого конца. Например сработает даже вот это: #include <iostream> struct Leaf { void hi() { std::cout << "ok"; } }; struct Proxy4 { Leaf* operator->() { static Leaf x; return &x; } }; struct Proxy3 { Proxy4 operator->() { return {}; } }; struct Proxy2 { Proxy3 operator->() { return {}; } }; struct Proxy { Proxy2 operator->() { return {}; } }; struct Box { Proxy operator->() { return {}; } }; int main() { Box b; b->hi(); } Я вот хочу куда-нибудь в шаблонный код загнать, пранкануть, таксказать, коллег.4,68%
  • 16 маяСижу на CppRussia. Пока тут каждый второй слайд первого же доклада - кандидат на пост сюда....3,77%
  • 1 авг.Я сначала подумал, что надо на такие мероприятия футболку брать с QR кодом канала на спине, пиарить канал на конфе. А потом подумал: меня же спалят как админа этого канала... Свят-свят-свят. P.S. новый пост зашедулен на понедельник :)3,27%
  • 21 июл.Те, кто со мной регулярно общаются, знают, что я ненавижу сраный move. И всю move семантику. И у меня даже готовился доклад про то, как я его ненавижу, с кучей веселых примеров, которые не приехали еще сюда в дурку. Где я собрал в кучку почему именно я его ненавижу, но там получилось очень много и очень неструктурированно, поэтому доклад до сих пор в разработке. Но вот один пример, который я выкину таки сюда. int main() { std::map<int, std::unique_ptr<int>> m; m.emplace(1, std::make_unique<int>(42)); auto p = std::make_unique<int>(100); auto [it, inserted] = m.emplace(std::make_pair(1, std::move(p))); } Здесь классика классика. Мапа из чего-то в в unique_ptr. Ну кто так не делал (здесь должна быть ссылке на open-source пример, но у меня жопа горит). Дальше мы делаем emplace. Return value A pair consisting of an iterator to the inserted element (or to the element that prevented the insertion) and a bool value set to true if and only if the insertion took place. Ну и вот второй emplace с ключом 1 вернет false. А что же произойдет с p? Он занулится, потому что мы его мувнули в функцию. int main() { std::map<int, std::unique_ptr<int>> m; m.emplace(1, std::make_unique<int>(42)); auto p = std::make_unique<int>(100); auto [it, inserted] = m.emplace(std::make_pair(1, std::move(p))); std::cout << std::boolalpha; std::cout << "inserted = " << inserted << '\n'; std::cout << "p is null = " << (p == nullptr) << '\n'; std::cout << "*it->second = " << *it->second << '\n'; } Выводит inserted = false p is null = true *it->second = 42 Тоесть в мапу значение не поместили, а из указателя оно пропало... Восхитительно. Притом что если делать std::move "на месте" такого не происходит. int main() { auto p = std::make_unique<int>(100); std::move(p); std::cout << std::boolalpha; std::cout << "p is null = " << (p.get() == nullptr) << '\n'; } p is null = false Да, это чинится тем, что мы убираем make_pair, но это в тестовом примере, где это легко заметить. И когда уже знаешь проблему. Я задолбался ловить такие приколы с move.2,58%
  • 10 июл.Вместо дурки я сегодня продолжаю пересматривать доклад великолепного Константина Владимирова. Жду не дождусь, когда он появится в открытом доступе, чтобы поскидывать интересные таймкоды. Это будет легко, просто скриптом каждые две минуты написать.... Вместо дурки хочу поделиться интересной конструкцией, о которой до этого не знал, но почерпнул из того доклада. Пусть у нас есть класс: template<typename T> struct S { S(T) { std::cout << "S(T)" << std::endl; } template<typename Iter> S(Iter beg, Iter end) { (beg); (end); std::cout << "S(Iter beg, Iter end)" << std::endl; } }; Если мы напишем что-то такое: int a = 3; auto s1 = S(a); static_assert(std::same_as<decltype(s1), S<int>> ); То оно нормально скомпилируется. А вот если мы попробуем как-то тригернуть второй конструктор: std::vector<double> v; auto s2 = S(v.begin(), v.end()); То ожидаемо упадем с ошибкой, смысл которой сводится к тому, что компилятор не знает, какой именно тип выводить. error: no viable constructor or deduction guide for deduction of template arguments of 'S' Что довольно логично. Но оказывается, есть возможность указать "хинт" для того, какой тип должен выводить конструктор класса! template<typename Iter> S(Iter, Iter) -> S<typename std::iterator_traits<Iter>::value_type>; И тогда вот это компилируется: https://godbolt.org/z/MaPo7PsP4 std::vector<double> v; auto s2 = S(v.begin(), v.end()); static_assert(std::same_as<decltype(s2), S<double>> ); У Константина дальше в докладе было всякое, какие у этого есть побочные эффекты. Посмотрите доклад потом сами. Много примеров есть прямо вот тут: https://eel.is/c%2B%2Bdraft/over.match.class.deduct А меня просто понесло. Мне так нравятся типы, которые меняются при каждом копировании, кто бы знал (и да, весь пост написан только ради этой ржаки): https://godbolt.org/z/4EMnjWsza #include <concepts> struct Charmander {}; struct Charmeleon {}; struct Charizard {}; template<typename Form> struct Pokemon { Pokemon(Form) {} template<typename OtherForm> Pokemon(const Pokemon<OtherForm>&) {} }; Pokemon(const Pokemon<Charmander>&) -> Pokemon<Charmeleon>; Pokemon(const Pokemon<Charmeleon>&) -> Pokemon<Charizard>; Pokemon(const Pokemon<Charizard>&) -> Pokemon<Charmander>; int main() { Pokemon p1 = Charmander{}; Pokemon p2 = p1; Pokemon p3 = p2; Pokemon p4 = p3; static_assert(std::same_as< decltype(p1), Pokemon<Charmander> >); static_assert(std::same_as< decltype(p2), Pokemon<Charmeleon> >); static_assert(std::same_as< decltype(p3), Pokemon<Charizard> >); static_assert(std::same_as< decltype(p4), Pokemon<Charmander> >); }2,56%
  • 3 авг.Убедительная просьба - обновляйте компиляторы. Помимо внедрения множества способов выстрелить себе в ногу, новые версии компиляторов чинят разные веселые баги. Вот к примеру вот этот код: struct S { int x; auto foo() { return [*this](this auto&& self) { __builtin_printf("%d ", x); x = 10; }; } }; Возвращаемая лямбда возвращаемая из метода foo капчурит this, и потом модифицирует значение. Но что будет, если мы сделаем лямбду константной? int main() { S s{ 5 }; const auto l = s.foo(); l(); s.x = 15; l(); __builtin_printf("%d\n", s.x); } вот тут clang18.1 нормально сработает и модифицирует константный класс. clang19.1 выдаст ошибку компиляции. А gcc15.2 упадет с segfault https://godbolt.org/z/8qqbehqhE2,36%
  • 5 июн.Вот сколько я дурки повидал (у меня канал про это целый заведен так-то !!!) но с удивлением я осознал, что вот такая штука %:include <iostream> int main() <% int a<:3:> = <% 10, 20, 30 %>; std::cout << a<:1:>; %> Компилируется во всех комипляторах... Потому что язык заботливо сохранил диграфы — на случай, если ваша клавиатура из 1973 года. Пипец какая жесть.2,13%
  • 16 июл.Приятно видеть, что C++ настолько тщательно следит за обратной совместимостью. Мы столько боли ради этой обратной совместимости пережили. И теперь вдвойне приятно видеть, что поменяться может семантика вообще всего что угодно. Например, вот это: decltype(auto) f(int&& x) { return (x); } static_assert(std::same_as<decltype(f(1)), int&>); Оно нормально скомпилируется в С++20, но упадет на static_assert в C++23. Тоесть не просто перестанет компилироваться, а тихой сапой скомпилируется "в другое". Насколько понимаю, связано вот с этим: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2266r3.html Но меня уже разоблачили, плюсовик я не настоящий и гениальности очередного замысла могу не понять. Как хорошо, что у меня канал не образовательный а развлекательный 🙂2,07%
  • 27 апр.Маленька классика на этой неделе. Что выведет вот этот код? #include <iostream> int main() { int x = 42; std::cout << sizeof(++x) << '\n'; std::cout << x << '\n'; } Да, все верно: ``` 4 42 ``` Тут все просто: sizeof не вычисляет выражение. Вообще никак. То есть ++x написан, вы его видите, компилятор его видит, Бог его видит, но реально инкремента не происходит. Только clang немного поплюется warning-ами Ну чтож... всего лишь еще один повод угодить в дурку.2,05%
  • 26 июн.Мой дорогой друг вот с этого канала о С++: @thisnotes Прислал мне просто восхитительную вещь. Давайте посмотрим вот на этот код const std::vector<Obj>& vec = (argc > 1) ? std::vector<Obj>{} : ready_vec; Вот тут зашита просто удивительая проблема. Нет, это не ссылка на временный объект, потому что const продлевает время жизни ссылки. Интересный факт, что вот такой код #include <iostream> #include <vector> struct Obj { explicit Obj() {} Obj(const Obj&) { std::cout << "copy\n"; } }; int main(int argc) { auto ready_vec = std::vector<Obj>{}; ready_vec.reserve(1); ready_vec.emplace_back(); const std::vector<Obj>& vec = (argc > 1) ? std::vector<Obj>{} : ready_vec; return vec.size(); } Компилируется в 16 строк ассемблера, а вот если мы сделаем вот так: #include <iostream> #include <vector> struct Obj { explicit Obj() {} Obj(const Obj&) { std::cout << "copy\n"; } }; int main(int argc) { auto ready_vec = std::vector<Obj>{}; ready_vec.reserve(1); ready_vec.emplace_back(); const auto empty_vec = std::vector<Obj>{}; const std::vector<Obj>& vec = (argc > 1) ? empty_vec : ready_vec; return vec.size(); } Компилируется в 6 строк ассемблерного кода. А фокус в том, что тернарный оператор в этой строчке const std::vector<Obj>& vec = (argc > 1) ? std::vector<Obj>{} : ready_vec; должен оба аргумента конвертировать к одному типу. А временный объект к ссылке сконвертировать не получится, поэтому и второй аргумент к ссылке не может быть конвертирован, и создается временная копия вектора. Думаю, в комментариях он подскажет, был ли этот пример найден в продуктовой задаче...2,01%
  • 25 маяСегодняшняя рубрика называется "обычный шаблонный код, который компилируется только после жертвоприношения". Что выведет вот этот код? cpp #include <iostream> #include <vector> template <class T> struct LoggedVector : std::vector<T> { void Dump() const { if (empty()) { std::cout << "empty\n"; return; } std::cout << "size = " << size() << '\n'; } }; int main() { LoggedVector<int> v; v.Dump(); } Иииииии... Правильный отвееееет..... Да, вы правы, он ничего не выведет. Упадет на ошибке компиляции: ``` error: use of undeclared identifier 'empty' ``` Небольшой отступ чтобы код под спойлером не бросался в глаза Что тут не так. На самом деле надо делать или так: void Dump() const { if (this->empty()) { std::cout << "empty\n"; return; } std::cout << "size = " << this->size() << '\n'; } Или вот так: using std::vector<T>::size; using std::vector<T>::empty; Ты literally видишь перед собой size() и empty(), они вот там, в базовом классе, рукой подать. Но компилятор такой: Нет. В шаблонах я сначала притворяюсь, что базового класса почти не существует. Особенно приятно при большом рефакторинге, когда меняешь не-шаблонный класс на шаблонный, а он потом в произвольных местах кода ломается...1,71%