Маленька классика на этой неделе.
Что выведет вот этот код?
Да, все верно:
```
4
42
```
Тут все просто: sizeof не вычисляет выражение. Вообще никак.
То есть ++x написан,
вы его видите,
компилятор его видит,
Бог его видит,
но реально инкремента не происходит.
Только clang немного поплюется warning-ами
Ну чтож... всего лишь еще один повод угодить в дурку.
Что выведет вот этот код?
#include <iostream>
int main() {
int x = 42;
std::cout << sizeof(++x) << '\n';
std::cout << x << '\n';
}
Да, все верно:
4
42
```
Тут все просто: sizeof не вычисляет выражение. Вообще никак.
То есть ++x написан,
вы его видите,
компилятор его видит,
Бог его видит,
но реально инкремента не происходит.
Только clang немного поплюется warning-ами
Ну чтож... всего лишь еще один повод угодить в дурку.
godbolt.org
Compiler Explorer - C++
int main() {
int x = 42;
std::cout << sizeof(++x) << '\n';
std::cout << x << '\n';
}
int x = 42;
std::cout << sizeof(++x) << '\n';
std::cout << x << '\n';
}
🔥20😁11❤3
Пример из того самого доклада.
Это просто прекрасное. Я где-то слышал утверждение, что лямбы имеют zero cost, что должны соптимизироваться во что-то такое же, как и исхордный код.
Ну так вот для такого кода:
У меня сработало в трех случаях из четырех. В четвертом вышло
Счастливого дебага с*****.
Это просто прекрасное. Я где-то слышал утверждение, что лямбы имеют zero cost, что должны соптимизироваться во что-то такое же, как и исхордный код.
Ну так вот для такого кода:
int main() {
auto div = [](double a, double b) { return a / b; };
double a = 0.5;
double b = 0.01;
std::cout << (int)(a / b) << std::endl;
std::cout << (int)div(a, b) << std::endl;
}
У меня сработало в трех случаях из четырех. В четвертом вышло
49
50
Telegram
C++: Хроники Дурки🚑
Мне тут мой дорогой друг присоветовал (подписывайтесь на его boosty !)
подрезать из одного доклада примерчики для этого канала. И был абсолютно прав!
Я еще не досмотрел доклад, но в нем даже классические и всем известные примеры можно показать каким-то…
подрезать из одного доклада примерчики для этого канала. И был абсолютно прав!
Я еще не досмотрел доклад, но в нем даже классические и всем известные примеры можно показать каким-то…
😱12🤯8😁2❤1🥴1
Есть вот такая шляпа:
Что будет выведено?
Если запускать gcc/clang с
То они взворвуться всякими ошибками на доступ к памяти.
Без них
clang выводит:
icc выводит:
gcc и msvc не выводит ничего....
Ну это да, на самом деле тут дырявый ад, что Items возвращает ссылку на внутреннюю переменную временного объекта, который должен помереть до того как по нему пройдется цикл.
А под капотом - так называемый "13-летний баг", историю которого можно отследить вот по этим ссылкам:
https://cplusplus.github.io/CWG/issues/900.html
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2012r2.pdf
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2644r1.pdf
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2718r0.html
Какие выводы мы можем сделать по итогу этих ссылок? Что в С++23 починили таки эту проблему.
Содержательно fix такой: в [class.temporary] добавили четвёртый специальный контекст, в котором временный объект живёт дольше обычного — если он создан в for-range-initializer range-based for, то его lifetime продлевается на жизнь скрытой ссылки, то есть фактически на весь цикл.
И оно поддержано уже в gcc/clang (но не других компиляторах).
Пример с теми же санитайзерами выводит:
Приятно, что всего через 13 лет один из самых болезненных багов из С++ ушел...
struct Buffer {
std::vector<int> data{1, 2, 3};
const std::vector<int>& Items() const {
return data;
}
};
Buffer MakeBuffer() {
return {};
}
int main() {
for (int x : MakeBuffer().Items()) {
std::cout << x << ' ';
}
std::cout << '\n';
}
Что будет выведено?
Если запускать gcc/clang с
-fsanitize=address -fsanitize=undefined
То они взворвуться всякими ошибками на доступ к памяти.
==1==ERROR: AddressSanitizer: stack-use-after-scope on address 0x6cc5bdef0060 at pc 0x5806274e084a bp 0x7ffc913eefb0 sp 0x7ffc913eefa8
READ of size 8 at 0x6cc5bdef0060 thread T0
#0 0x5806274e0849 in __gnu_cxx::__normal_iterator<int const*, std::vector<int, std::allocator<int>>>::__normal_iterator(int const* const&) /opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/16.0.1/../../../../include/c++/16.0.1/bits/stl_iterator.h:1059:20
Без них
clang выводит:
692359176 -2055096448 -2055096448
icc выводит:
163141 0 -143200117
gcc и msvc не выводит ничего....
Ну это да, на самом деле тут дырявый ад, что Items возвращает ссылку на внутреннюю переменную временного объекта, который должен помереть до того как по нему пройдется цикл.
А под капотом - так называемый "13-летний баг", историю которого можно отследить вот по этим ссылкам:
https://cplusplus.github.io/CWG/issues/900.html
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2012r2.pdf
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2644r1.pdf
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2718r0.html
Какие выводы мы можем сделать по итогу этих ссылок? Что в С++23 починили таки эту проблему.
Содержательно fix такой: в [class.temporary] добавили четвёртый специальный контекст, в котором временный объект живёт дольше обычного — если он создан в for-range-initializer range-based for, то его lifetime продлевается на жизнь скрытой ссылки, то есть фактически на весь цикл.
The fourth context is when a temporary object other than a function parameter object is created in the for-range-initializer of a range-based for statement. If such a temporary object would otherwise be destroyed at the end of the for-range-initializer full-expression, the object persists for the lifetime of the reference initialized by the for-range-initializer.
И оно поддержано уже в gcc/clang (но не других компиляторах).
Пример с теми же санитайзерами выводит:
1 2 3
Приятно, что всего через 13 лет один из самых болезненных багов из С++ ушел...
godbolt.org
Compiler Explorer - C++
struct Buffer {
std::vector<int> data{1, 2, 3};
const std::vector<int>& Items() const {
return data;
}
};
Buffer MakeBuffer() {
return {};
}
int main() {
for (int x : MakeBuffer().Items()) {
std::cout << x << ' ';
…
std::vector<int> data{1, 2, 3};
const std::vector<int>& Items() const {
return data;
}
};
Buffer MakeBuffer() {
return {};
}
int main() {
for (int x : MakeBuffer().Items()) {
std::cout << x << ' ';
…
🔥24😭6👍3❤1
Посмеемся?
Что выведет?
Ответ конечно `bool`:
Почему так?
Потому что перегрузки из Base в Derived скрываются целиком, если в наследнике появился метод с тем же именем.
И дальше d.Set("hello") уже ищет только среди перегрузок Derived.
А const char* в bool конвертируется просто замечательно.
И по нашей любимой традиции - ни одного ворнинга ни в одном из компиляторов.
#include <iostream>
#include <string_view>
struct Base {
void Set(std::string_view) { std::cout << "string\n"; }
void Set(int) { std::cout << "int\n"; }
};
struct Derived : Base {
void Set(bool) { std::cout << "bool\n"; }
};
int main() {
Derived d;
d.Set("hello");
}
Что выведет?
Почему так?
Потому что перегрузки из Base в Derived скрываются целиком, если в наследнике появился метод с тем же именем.
И дальше d.Set("hello") уже ищет только среди перегрузок Derived.
А const char* в bool конвертируется просто замечательно.
И по нашей любимой традиции - ни одного ворнинга ни в одном из компиляторов.
godbolt.org
Compiler Explorer - C++
struct Base {
void Set(std::string_view) { std::cout << "string\n"; }
void Set(int) { std::cout << "int\n"; }
};
struct Derived : Base {
void Set(bool) { std::cout << "bool\n"; }
};
int main() {
Derived d;
d.Set("hello");…
void Set(std::string_view) { std::cout << "string\n"; }
void Set(int) { std::cout << "int\n"; }
};
struct Derived : Base {
void Set(bool) { std::cout << "bool\n"; }
};
int main() {
Derived d;
d.Set("hello");…
🔥23😁10☃2❤1
Сижу на CppRussia.
Пока тут каждый второй слайд первого же доклада - кандидат на пост сюда....
Пока тут каждый второй слайд первого же доклада - кандидат на пост сюда....
😁56💯8🔥4
Не могу не поделиться самым веселым, на мой взгляд, примером из доклада великолепного Константина Владимирова. Он делал анонс доклада в своем tg канале.
Пример вот такой:
В чем тут цимес. У нас
Если же мы явно укажем пустые треугольные скобки вот так:
То типы, внезапно, станут одинаковыми. Ну, мы один раз объявили тип, и две переменные этого типа.
А теперь вопрос в зал. А что если мы определим эти две переменные точно так же, как во втором варианте, но только без явного указания треугольных скобок?
Давайте вы попробуете угадать?
Ставя треугольные скобки мы исключаем вывод типов. Мы явно указываем, какой тип мы используем.
Но если у нас есть вывод типов, у нас компиляторы начинают вести себя по-разному.
clang падает с ошибкой
```
error: template arguments deduced as 'S<(lambda at <source>:4:20){}>' in declaration of 'a' and deduced as 'S<(lambda at <source>:4:20){}>' in declaration of 'b'
7 | S a, b;
```
А gcc считает, что это два разных типа.
```
static_assert(
!std::is_same_v<decltype(a), decltype (b)>
);
```
Пруф .
Вцелом доклад Константина был просто прекрасным, и я, наверное, понатырю сюда еще примеров из его доклада через пару месяцев. А когда он выйдет в открытый доступ - обязательно дам ссылку. Я был просто в восторге от дурки, которую он показывал.
Пример вот такой:
template <auto T = []{}>
struct S {};
S a; S b;
В чем тут цимес. У нас
T - это лямбда. И если мы таким образом определяем переменные, то в тип S записываются разные лямбды, и у нас получаются два разных типа:
static_assert(
!std::is_same_v<decltype(a), decltype (b)>
);
Если же мы явно укажем пустые треугольные скобки вот так:
S<> a, b;
static_assert(
std::is_same_v<decltype(a), decltype (b)>
);
То типы, внезапно, станут одинаковыми. Ну, мы один раз объявили тип, и две переменные этого типа.
А теперь вопрос в зал. А что если мы определим эти две переменные точно так же, как во втором варианте, но только без явного указания треугольных скобок?
S a, b;
Давайте вы попробуете угадать?
Но если у нас есть вывод типов, у нас компиляторы начинают вести себя по-разному.
clang падает с ошибкой
```
error: template arguments deduced as 'S<(lambda at <source>:4:20){}>' in declaration of 'a' and deduced as 'S<(lambda at <source>:4:20){}>' in declaration of 'b'
7 | S a, b;
```
А gcc считает, что это два разных типа.
```
static_assert(
!std::is_same_v<decltype(a), decltype (b)>
);
```
Вцелом доклад Константина был просто прекрасным, и я, наверное, понатырю сюда еще примеров из его доклада через пару месяцев. А когда он выйдет в открытый доступ - обязательно дам ссылку. Я был просто в восторге от дурки, которую он показывал.
Telegram
C++ and other lectures
Всем привет. Кто идёт на C++ Russia из моих уважаемых подписчиков, обратите пожалуйста внимание на изменения в программе, внесённые в последний момент.
https://cppconf.ru/schedule/table/#day-2
Теперь мой доклад открывает конференцию в субботу утром, а Антон…
https://cppconf.ru/schedule/table/#day-2
Теперь мой доклад открывает конференцию в субботу утром, а Антон…
👍18🤯11❤3🔥2
Сегодняшняя рубрика называется "обычный шаблонный код, который компилируется только после жертвоприношения".
Что выведет вот этот код?
Иииииии... Правильный отвееееет.....
Да, вы правы, он ничего не выведет.
Упадет на ошибке компиляции:
```
error: use of undeclared identifier 'empty'
```
Небольшой отступ чтобы код под спойлером не бросался в глаза
Что тут не так.
На самом деле надо делать или так:
Или вот так:
Ты literally видишь перед собой size() и empty(), они вот там, в базовом классе, рукой подать.
Но компилятор такой:
Нет.
В шаблонах я сначала притворяюсь, что базового класса почти не существует.
Особенно приятно при большом рефакторинге, когда меняешь не-шаблонный класс на шаблонный, а он потом в произвольных местах кода ломается...
Что выведет вот этот код?
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(), они вот там, в базовом классе, рукой подать.
Но компилятор такой:
Нет.
В шаблонах я сначала притворяюсь, что базового класса почти не существует.
Особенно приятно при большом рефакторинге, когда меняешь не-шаблонный класс на шаблонный, а он потом в произвольных местах кода ломается...
godbolt.org
Compiler Explorer - C++
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>…
struct LoggedVector : std::vector<T> {
void Dump() const {
if (empty()) {
std::cout << "empty\n";
return;
}
std::cout << "size = " << size() << '\n';
}
};
int main() {
LoggedVector<int>…
❤29🍌1
Разбираем письма читателей.
Нам прислали вот такой вот код:
Что в нем примечательного. Под gcc/clang у нас в консоль ничего не запишется.
Для MSVC x64 запишется
А для MSVC x86 вообще случится страшное:
Ну и чтобы не оставлять предложку совсем уж без изменений, я добавлю от себя немного дурки. Если в вызов функции добавить скобки:
То, внезапно, в gcc/clang мы тоже будем печатать строчку. А вот в MSVC x86 все еще будет возвращаться ненеулевой код....
Нам прислали вот такой вот код:
#include <memory>
#include <iostream>
namespace user {
struct user_type {};
using my_type = std::shared_ptr<user_type>;
void tie(my_type const&, my_type const&)
{
std::cout << "user::tie\n";
}
void oups()
{
my_type t1;
my_type t2;
tie(t1, t2);
}
} // namespace user
int main()
{
user::oups();
}
Что в нем примечательного. Под gcc/clang у нас в консоль ничего не запишется.
Program returned: 0
Для MSVC x64 запишется
Program returned: 0
user::tie
А для MSVC x86 вообще случится страшное:
Program returned: 3221225595
Ну и чтобы не оставлять предложку совсем уж без изменений, я добавлю от себя немного дурки. Если в вызов функции добавить скобки:
void oups()
{
my_type t1;
my_type t2;
(tie)(t1, t2);
}
То, внезапно, в gcc/clang мы тоже будем печатать строчку. А вот в MSVC x86 все еще будет возвращаться ненеулевой код....
godbolt.org
Compiler Explorer - C++
namespace user {
struct user_type {};
using my_type = std::shared_ptr<user_type>;
void tie(my_type const&, my_type const&)
{
std::cout << "user::tie\n";
}
void oups()
{
my_type t1;
my_type t2;
tie(t1, t2);
}
} // namespace user
int…
struct user_type {};
using my_type = std::shared_ptr<user_type>;
void tie(my_type const&, my_type const&)
{
std::cout << "user::tie\n";
}
void oups()
{
my_type t1;
my_type t2;
tie(t1, t2);
}
} // namespace user
int…
🤯29❤5😱3
Вот сколько я дурки повидал (у меня канал про это целый заведен так-то !!!) но с удивлением я осознал, что вот такая штука
Компилируется во всех комипляторах...
Потому что язык заботливо сохранил диграфы — на случай, если ваша клавиатура из 1973 года.
Пипец какая жесть.
%:include <iostream>
int main() <%
int a<:3:> = <% 10, 20, 30 %>;
std::cout << a<:1:>;
%>
Компилируется во всех комипляторах...
Потому что язык заботливо сохранил диграфы — на случай, если ваша клавиатура из 1973 года.
Пипец какая жесть.
godbolt.org
Compiler Explorer - C++
%:include <iostream>
int main() <%
int a<:3:> = <% 10, 20, 30 %>;
std::cout << a<:1:>;
%>
int main() <%
int a<:3:> = <% 10, 20, 30 %>;
std::cout << a<:1:>;
%>
😁29🤯15😐2
В разных новых (уже скоро будет 10 лет) стандартах иногда бывают мелкие но очень приятные изменения.
Вот такой код
Был валиден до С++17, но
Вот так вот.
Вот такой код
#include <iostream>
int main() {
bool b = true;
b++;
std::cout << b;
}
Был валиден до С++17, но
<source>:6:5: error: use of an operand of type 'bool' in 'operator++' is forbidden in C++17
Вот так вот.
godbolt.org
Compiler Explorer - C++
int main() {
bool b = true;
b++;
std::cout << b;
}
bool b = true;
b++;
std::cout << b;
}
❤19👍9
Сорян, я выяснил, что у меня давно не было публикаций... А у меня просто один пост пропал. Не могу его найти ни в отложках ни в опубликованных...
Такчто сейчас перетасую расписание и пошел искать.
Такчто сейчас перетасую расписание и пошел искать.
🌚21❤4🤝2
Повторяю пропавший пост.
Он был о докладе Евгения Ерохина с прошедшего CppRussia. С моими пространными размышлениями о том, какие у него сложные зубодробильные доклады и как надо каждый слайд ставить на паузу и уходить получать доп образования, чтобы не терять контекст...
А пример, который хотел у него подрезать для этого канала был такой:
Утверждается, что это пример из реальной жизни, а постановка четырех NoOperation смещает операцию так, что branch prediction работает сильно лучше и значительно улучшает производительность.
Вот такая она оптимизация в 2026 году....
P.S. Если я кому-то кидал текст изначального поста - дайте знать. Там было сильно больше интересного, но больно уж мне этот пример понравился.
P.P.S. А вообще его доклад (и другие доклады) рекомендую к просмотру хотябы из чуства мазохизма. Если у кого-то есть иллюзии, что он шарит в IT - самое время разочароваться в собственных знаниях :)
Он был о докладе Евгения Ерохина с прошедшего CppRussia. С моими пространными размышлениями о том, какие у него сложные зубодробильные доклады и как надо каждый слайд ставить на паузу и уходить получать доп образования, чтобы не терять контекст...
А пример, который хотел у него подрезать для этого канала был такой:
int fn(int a, int b) {
int res = 0;
asm volatile ("nop");
asm volatile ("nop");
asm volatile ("nop");
asm volatile ("nop");
if (a > b) { // проблемный бренч
res = std::rand();
}
res = std::max(res, a);
return res;
}
Утверждается, что это пример из реальной жизни, а постановка четырех NoOperation смещает операцию так, что branch prediction работает сильно лучше и значительно улучшает производительность.
Вот такая она оптимизация в 2026 году....
P.S. Если я кому-то кидал текст изначального поста - дайте знать. Там было сильно больше интересного, но больно уж мне этот пример понравился.
P.P.S. А вообще его доклад (и другие доклады) рекомендую к просмотру хотябы из чуства мазохизма. Если у кого-то есть иллюзии, что он шарит в IT - самое время разочароваться в собственных знаниях :)
😭23👏5⚡1
Мой дорогой друг вот с этого канала о С++:
@thisnotes
Прислал мне просто восхитительную вещь.
Давайте посмотрим вот на этот код
Вот тут зашита просто удивительая проблема. Нет, это не ссылка на временный объект, потому что
Интересный факт, что вот такой код
Компилируется в 16 строк ассемблера, а вот если мы сделаем вот так:
Компилируется в 6 строк ассемблерного кода.
А фокус в том, что тернарный оператор в этой строчке
должен оба аргумента конвертировать к одному типу. А временный объект к ссылке сконвертировать не получится, поэтому и второй аргумент к ссылке не может быть конвертирован, и создается временная копия вектора.
Думаю, в комментариях он подскажет, был ли этот пример найден в продуктовой задаче...
@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;
должен оба аргумента конвертировать к одному типу. А временный объект к ссылке сконвертировать не получится, поэтому и второй аргумент к ссылке не может быть конвертирован, и создается временная копия вектора.
Думаю, в комментариях он подскажет, был ли этот пример найден в продуктовой задаче...
godbolt.org
Compiler Explorer - C++
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)
…
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)
…
🤯17🔥9😁2🌚2🫡1
Ой, я когда-нибудь брошу этот сраный язык.
Давайте разберем по шагам.
Что тут происходит? Мы создаем объект типа
Смотрим, что такое у нас тип
Ага, значит оператор выдает экземпляр объекта типа Proxy, и вот у него вызывается функция
Смотрим, что такое структура
В структуре Proxy функции
Уберите детей от мониторов.
Вот этот код компилируется и выводит
Итак.
У нас С++ разворачивает оператор
Я вот хочу куда-нибудь в шаблонный код загнать, пранкануть, таксказать, коллег.
Давайте разберем по шагам.
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();
}
Я вот хочу куда-нибудь в шаблонный код загнать, пранкануть, таксказать, коллег.
godbolt.org
Compiler Explorer - C++
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();
}
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();
}
🔥28🤯25😁12❤3💊3🤡1
Вместо дурки я сегодня продолжаю пересматривать доклад великолепного Константина Владимирова.
Жду не дождусь, когда он появится в открытом доступе, чтобы поскидывать интересные таймкоды. Это будет легко, просто скриптом каждые две минуты написать....
Вместо дурки хочу поделиться интересной конструкцией, о которой до этого не знал, но почерпнул из того доклада.
Пусть у нас есть класс:
Если мы напишем что-то такое:
То оно нормально скомпилируется.
А вот если мы попробуем как-то тригернуть второй конструктор:
То ожидаемо упадем с ошибкой, смысл которой сводится к тому, что компилятор не знает, какой именно тип выводить.
Что довольно логично.
Но оказывается, есть возможность указать "хинт" для того, какой тип должен выводить конструктор класса!
И тогда вот это компилируется:
https://godbolt.org/z/MaPo7PsP4
У Константина дальше в докладе было всякое, какие у этого есть побочные эффекты. Посмотрите доклад потом сами.
Много примеров есть прямо вот тут:
https://eel.is/c%2B%2Bdraft/over.match.class.deduct
А меня просто понесло. Мне так нравятся типы, которые меняются при каждом копировании, кто бы знал (и да, весь пост написан только ради этой ржаки):
https://godbolt.org/z/4EMnjWsza
Жду не дождусь, когда он появится в открытом доступе, чтобы поскидывать интересные таймкоды. Это будет легко, просто скриптом каждые две минуты написать....
Вместо дурки хочу поделиться интересной конструкцией, о которой до этого не знал, но почерпнул из того доклада.
Пусть у нас есть класс:
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>
>);
}
Telegram
C++ and other lectures
Всем привет. Кто идёт на C++ Russia из моих уважаемых подписчиков, обратите пожалуйста внимание на изменения в программе, внесённые в последний момент.
https://cppconf.ru/schedule/table/#day-2
Теперь мой доклад открывает конференцию в субботу утром, а Антон…
https://cppconf.ru/schedule/table/#day-2
Теперь мой доклад открывает конференцию в субботу утром, а Антон…
😁29👌4❤2🤯2
Приятно видеть, что C++ настолько тщательно следит за обратной совместимостью.
Мы столько боли ради этой обратной совместимости пережили.
И теперь вдвойне приятно видеть, что поменяться может семантика вообще всего что угодно.
Например, вот это:
Оно нормально скомпилируется в С++20, но упадет на static_assert в C++23. Тоесть не просто перестанет компилироваться, а тихой сапой скомпилируется "в другое".
Насколько понимаю, связано вот с этим:
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2266r3.html
Но меня уже разоблачили, плюсовик я не настоящий и гениальности очередного замысла могу не понять. Как хорошо, что у меня канал не образовательный а развлекательный 🙂
Мы столько боли ради этой обратной совместимости пережили.
И теперь вдвойне приятно видеть, что поменяться может семантика вообще всего что угодно.
Например, вот это:
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
Но меня уже разоблачили, плюсовик я не настоящий и гениальности очередного замысла могу не понять. Как хорошо, что у меня канал не образовательный а развлекательный 🙂
godbolt.org
Compiler Explorer - C++
decltype(auto) f(int&& x)
{
return (x);
}
static_assert(std::same_as<decltype(f(1)), int&>);
int main(){}
{
return (x);
}
static_assert(std::same_as<decltype(f(1)), int&>);
int main(){}
😁21❤5🥴2🔥1🗿1
Те, кто со мной регулярно общаются, знают, что я ненавижу сраный move.
И всю move семантику. И у меня даже готовился доклад про то, как я его ненавижу, с кучей веселых примеров, которые не приехали еще сюда в дурку. Где я собрал в кучку почему именно я его ненавижу, но там получилось очень много и очень неструктурированно, поэтому доклад до сих пор в разработке.
Но вот один пример, который я выкину таки сюда.
Здесь классика классика.
Мапа из чего-то в в unique_ptr. Ну кто так не делал (здесь должна быть ссылке на open-source пример, но у меня жопа горит).
Дальше мы делаем emplace.
Ну и вот второй emplace с ключом 1 вернет false.
А что же произойдет с p?
Он занулится, потому что мы его мувнули в функцию.
Выводит
Тоесть в мапу значение не поместили, а из указателя оно пропало... Восхитительно.
Притом что если делать std::move "на месте" такого не происходит.
Да, это чинится тем, что мы убираем
Я задолбался ловить такие приколы с 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.
godbolt.org
Compiler Explorer - C++
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…
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…
🤷♂20😁11🤡3👎2😭2🔥1
Убедительная просьба - обновляйте компиляторы.
Помимо внедрения множества способов выстрелить себе в ногу, новые версии компиляторов чинят разные веселые баги.
Вот к примеру вот этот код:
Возвращаемая лямбда возвращаемая из метода
вот тут clang18.1 нормально сработает и модифицирует константный класс. clang19.1 выдаст ошибку компиляции. А gcc15.2 упадет с segfault
https://godbolt.org/z/8qqbehqhE
Помимо внедрения множества способов выстрелить себе в ногу, новые версии компиляторов чинят разные веселые баги.
Вот к примеру вот этот код:
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/8qqbehqhE
godbolt.org
Compiler Explorer - C++
struct S {
int x;
auto foo() {
return [*this](this auto&& self) {
__builtin_printf("%d ", x);
x = 10;
};
}
};
int main() {
S s{ 5 };
const auto l = s.foo();
l();
s.x = 15;
l();
__builtin_printf("%d\n", s.x);
}
int x;
auto foo() {
return [*this](this auto&& self) {
__builtin_printf("%d ", x);
x = 10;
};
}
};
int main() {
S s{ 5 };
const auto l = s.foo();
l();
s.x = 15;
l();
__builtin_printf("%d\n", s.x);
}
💊20🤔5
Ладно, разумеется прошлый пост был набросом.
Никогда не обновляйте компиляторы без очень серьезных на то причин. Мало ли что там сломается, а у вас не будет команды все это чинить.
Вот к примеру.
Вложенные лямбды с параметр паком.
В clang 17 они нормально компилируются.
В clang 20 они крашат компилятор.
В clang 22 это починили и они компилируются.
В текущем транке снова ломают компилятор.
Не выходи из комнаты, не совершай ошибку. Компилятор обновляют слабаки и трусы.
Никогда не обновляйте компиляторы без очень серьезных на то причин. Мало ли что там сломается, а у вас не будет команды все это чинить.
Вот к примеру.
Вложенные лямбды с параметр паком.
#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 это починили и они компилируются.
В текущем транке снова ломают компилятор.
Не выходи из комнаты, не совершай ошибку. Компилятор обновляют слабаки и трусы.
godbolt.org
Compiler Explorer - C++
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>{});…
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>{});…
🤡13🦄7😁3🤔2❤1🔥1💩1👾1