C++: Хроники Дурки🚑
الذهاب إلى القناة على Telegram
Очень люблю C++, но это скорее уже стокгольмский синдром. Постоянно нахожу способы стрельнуть себе в ногу.
إظهار المزيد915
المشتركون
+324 ساعات
+57 أيام
+430 أيام
جاري تحميل البيانات...
القنوات المماثلة
لا توجد بيانات
هل تواجه مشاكل؟ يرجى تحديث الصفحة أو الاتصال بمدير الدعم الخاص بنا.
سحابة العلامات
الإشارات الواردة والصادرة
---
---
---
---
---
---
جذب المشتركين
أغسطس '26
أغسطس '26
+16
في 0 قنوات
يوليو '26
+34
في 0 قنوات
Get PRO
يونيو '26
+136
في 1 قنوات
Get PRO
مايو '26
+319
في 1 قنوات
Get PRO
أبريل '26
+27
في 0 قنوات
Get PRO
مارس '26
+128
في 2 قنوات
Get PRO
فبراير '26
+8
في 0 قنوات
Get PRO
يناير '26
+227
في 1 قنوات
Get PRO
ديسمبر '25
+77
في 1 قنوات
| التاريخ | نمو المشتركين | الإشارات | القنوات | |
| 27 أغسطس | 0 | |||
| 26 أغسطس | +4 | |||
| 25 أغسطس | 0 | |||
| 24 أغسطس | +1 | |||
| 23 أغسطس | +1 | |||
| 22 أغسطس | 0 | |||
| 21 أغسطس | +2 | |||
| 20 أغسطس | 0 | |||
| 19 أغسطس | +2 | |||
| 18 أغسطس | 0 | |||
| 17 أغسطس | 0 | |||
| 16 أغسطس | 0 | |||
| 15 أغسطس | +1 | |||
| 14 أغسطس | 0 | |||
| 13 أغسطس | 0 | |||
| 12 أغسطس | 0 | |||
| 11 أغسطس | 0 | |||
| 10 أغسطس | 0 | |||
| 09 أغسطس | 0 | |||
| 08 أغسطس | 0 | |||
| 07 أغسطس | 0 | |||
| 06 أغسطس | 0 | |||
| 05 أغسطس | +2 | |||
| 04 أغسطس | +2 | |||
| 03 أغسطس | 0 | |||
| 02 أغسطس | +1 | |||
| 01 أغسطس | 0 |
منشورات القناة
Если кто не знаком с библиотекой nlohmann/json - она прекрасна. Мои мысли о том, как должна выглядеть библиотека для пользователя, она начала формировать еще году в 2012. (я гне нашел подтверждений, что она тогда уже была, но по ощущениям где-то тогда и появилась)
Но речь не об этом.
Как написано в cppreference, если ключи равны, то и хеши от них равны.
```
Given the following values:
h, an object of type std::hash<Key>.
k1 and k2, objects of type Key.
All following requirements are satisfied:
- If k1 == k2 is true, h(k1) == h(k2) is also true.
- Unless std::hash<Key> is a program-defined specialization, h(k1) will never throw an exception.
```
Иногда срабатывает даже если это объекты разных типов
const auto x1 = 42;
const auto x2 = 42u;
__builtin_printf("%b\n\n", x1 == x2);
__builtin_printf("%#08zx\n%#08zx\n",
std::hash<int>{}(x1),
std::hash<unsigned>{}(x2));
1 0x00002a 0x00002aА вот если вы упакуете те же типы в обертку типа json:
const auto j1 = json{x1};
const auto j2 = json{x2};
__builtin_printf("%b\n\n", j1 == j2);
__builtin_printf("%#08zx\n%#08zx\n",
std::hash<json>{}(j1),
std::hash<json>{}(j2));
Тут уже как повезет. Конкретно для этой библиотеке не сраслось:
1 0x286fec1b50 0x286fec1b91Потому что там есть своя специализация. И с ней это правило для hash не работает.
namespace std // NOLINT(cert-dcl58-cpp)
{
/// @brief hash value for JSON objects
/// @sa https://json.nlohmann.me/api/basic_json/std_hash/
NLOHMANN_BASIC_JSON_TPL_DECLARATION
struct hash<nlohmann::NLOHMANN_BASIC_JSON_TPL> // NOLINT(cert-dcl58-cpp)
{
std::size_t operator()(const nlohmann::NLOHMANN_BASIC_JSON_TPL& j) const
{
return nlohmann::detail::hash(j);
}
};
А потому эту штуку очень весело бывает использовать в hash-мапах, когда два одинаковых объекта могут стать разными ключами.
Сохраняем бдительность!!!!| 2 | #толькосвоимемы
Код взят тут.
if (dim == 0) idx_dim = i0;
else if (dim == 1) idx_dim = i1;
else if (dim == 2) idx_dim = i2;
else if (dim == 3) idx_dim = i3;
const struct htp_tensor * src = (idx_dim < src0->ne[dim]) ? src0 : src1;
uint32_t s0 = i0;
uint32_t s1 = i1;
uint32_t s2 = i2;
uint32_t s3 = i3;
if (dim == 0 && src == src1) s0 -= src0->ne[0];
if (dim == 1 && src == src1) s1 -= src0->ne[1];
if (dim == 2 && src == src1) s2 -= src0->ne[2];
if (dim == 3 && src == src1) s3 -= src0->ne[3];
Вообще по библиотеке много кода, который заставляет задумываться о вечном. | 859 |
| 3 | Ладно, искать баги в компиляторах весело, но недостаточно.
Давайте поиграемся в чуть более интересные игры.
Вспомним, что у нас в С++ есть правило: структура не может иметь нулевой размер. Потому что у ее инстанса должен быть адрес, и здесь С++ как-то забыл про Zero Cost и все такое.
Зато С++ вспомнил про Zero Cost, когда мы примешиваем пустую структуру в наследование другой.
struct Empty1 {};
struct Empty2 {};
struct NonEmpty {
int Value;
};
struct MyClass1
:public NonEmpty
,public Empty1
,public Empty2
{
};
struct MyClass2
:public Empty1
,public NonEmpty
,public Empty2
{
};
struct MyClass3
:public Empty1
,public Empty2
,public NonEmpty
{
};
static_assert(sizeof(NonEmpty) + sizeof(Empty1) + sizeof(Empty2) == 6);
static_assert(sizeof(int) == 4);
static_assert(sizeof(MyClass1) == 4);
static_assert(sizeof(MyClass2) == 4);
static_assert(sizeof(MyClass3) == 4);
int main() {}
Вот эта штука скомпилируется. Тоесть у нас структуры без размера имеют размер, но этот размер не добавляется к размеру общей структуры.
Всвязи с чем я упоролся, и написал вот такой класс:
struct Empty {
void zero() { std::memset(this, 0, sizeof(*this)); }
};
Свиду ничем не примечательный класс, который просто заполняет себя нулями. И это нормально работает для непустого класса, но пустой класс такими выкрутасами ломает соседние поля в классе, который его отнаследовал.
struct CompressedPair : Empty {
CompressedPair(Empty e, bool x) : Empty(e), b(x) { }
bool b;
};
int main() {
auto a = CompressedPair({}, true);
__builtin_printf("%b\n", a.b);
a.zero();
__builtin_printf("%b\n", a.b);
return 0;
}
Выводит
1
0
(да, да, это UB, я знаю, прекратите. Мы тут поржать собрались или где?)
Но где-то тут в безумии зараждается идея... У нас же есть no_unique_address.
А на gcc он кладет данные в padding других структур.
#include <iostream>
template <typename T>
struct optional {
union {
char foo;
T val;
};
[[no_unique_address]] bool engaged;
};
template <typename View>
struct reverse_view {
[[no_unique_address]] optional<typename View::iterator> cache;
View base_;
};
struct MyView {
using iterator = int*;
char begin_;
};
int main() {
std::cout << "sizeof = " << sizeof(reverse_view<MyView>) << std::endl; // clang - 24, gcc - 16
std::cout << "alignof = " << alignof(reverse_view<MyView>) << std::endl;
}
И тут я очень хотел развернуться мыслью, но быстро нашел уже закрытый баг с std::expected. (половина примеров выше взята из обсуждения этого бага, потому что они нагляднее чем то, с чем работал я).
Если коротко, то методы expected могли зачищать переменные вокруг себя, если те оказывались в из padding. Потому что emplace пересоздавал внутренний объект целиком.
Но, увы, примера воспроизводящего это дело у меня нет. А после этого примера все остальное рядом меркнет. Увы. | 986 |
| 4 | Ладно, разумеется прошлый пост был набросом.
Никогда не обновляйте компиляторы без очень серьезных на то причин. Мало ли что там сломается, а у вас не будет команды все это чинить.
Вот к примеру.
Вложенные лямбды с параметр паком.
#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 это починили и они компилируются.
В текущем транке снова ломают компилятор.
Не выходи из комнаты, не совершай ошибку. Компилятор обновляют слабаки и трусы. | 1 181 |
| 5 | Убедительная просьба - обновляйте компиляторы.
Помимо внедрения множества способов выстрелить себе в ногу, новые версии компиляторов чинят разные веселые баги.
Вот к примеру вот этот код:
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 | 1 435 |
| 6 | Я сначала подумал, что надо на такие мероприятия футболку брать с QR кодом канала на спине, пиарить канал на конфе.
А потом подумал: меня же спалят как админа этого канала... Свят-свят-свят.
P.S. новый пост зашедулен на завтра :) | 1 292 |
| 7 | Те, кто со мной регулярно общаются, знают, что я ненавижу сраный 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. | 1 719 |
| 8 | Приятно видеть, что 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
Но меня уже разоблачили, плюсовик я не настоящий и гениальности очередного замысла могу не понять. Как хорошо, что у меня канал не образовательный а развлекательный 🙂 | 1 446 |
| 9 | Вместо дурки я сегодня продолжаю пересматривать доклад великолепного Константина Владимирова.
Жду не дождусь, когда он появится в открытом доступе, чтобы поскидывать интересные таймкоды. Это будет легко, просто скриптом каждые две минуты написать....
Вместо дурки хочу поделиться интересной конструкцией, о которой до этого не знал, но почерпнул из того доклада.
Пусть у нас есть класс:
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>
>);
} | 1 420 |
| 10 | Ой, я когда-нибудь брошу этот сраный язык.
Давайте разберем по шагам.
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();
}
Я вот хочу куда-нибудь в шаблонный код загнать, пранкануть, таксказать, коллег. | 1 478 |
| 11 | Мой дорогой друг вот с этого канала о С++:
@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;
должен оба аргумента конвертировать к одному типу. А временный объект к ссылке сконвертировать не получится, поэтому и второй аргумент к ссылке не может быть конвертирован, и создается временная копия вектора.
Думаю, в комментариях он подскажет, был ли этот пример найден в продуктовой задаче... | 1 422 |
| 12 | Повторяю пропавший пост.
Он был о докладе Евгения Ерохина с прошедшего 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 - самое время разочароваться в собственных знаниях :) | 1 627 |
| 13 | Сорян, я выяснил, что у меня давно не было публикаций... А у меня просто один пост пропал. Не могу его найти ни в отложках ни в опубликованных... | 1 635 |
| 14 | В разных новых (уже скоро будет 10 лет) стандартах иногда бывают мелкие но очень приятные изменения.
Вот такой код
#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
Вот так вот. | 1 555 |
| 15 | Вот сколько я дурки повидал (у меня канал про это целый заведен так-то !!!) но с удивлением я осознал, что вот такая штука
%:include <iostream>
int main() <%
int a<:3:> = <% 10, 20, 30 %>;
std::cout << a<:1:>;
%>
Компилируется во всех комипляторах...
Потому что язык заботливо сохранил диграфы — на случай, если ваша клавиатура из 1973 года.
Пипец какая жесть. | 1 927 |
| 16 | Разбираем письма читателей.
Нам прислали вот такой вот код:
#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 все еще будет возвращаться ненеулевой код.... | 1 774 |
| 17 | Сегодняшняя рубрика называется "обычный шаблонный код, который компилируется только после жертвоприношения".
Что выведет вот этот код?
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 389 |
| 18 | Не могу не поделиться самым веселым, на мой взгляд, примером из доклада великолепного Константина Владимирова. Он делал анонс доклада в своем tg канале.
Пример вот такой:
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)>
);
```
Пруф.
Вцелом доклад Константина был просто прекрасным, и я, наверное, понатырю сюда еще примеров из его доклада через пару месяцев. А когда он выйдет в открытый доступ - обязательно дам ссылку. Я был просто в восторге от дурки, которую он показывал. | 9 985 |
| 19 | Сижу на CppRussia.
Пока тут каждый второй слайд первого же доклада - кандидат на пост сюда.... | 1 450 |
| 20 | Посмеемся?
#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");
}
Что выведет?
Ответ конечно `bool`:
Почему так?
Потому что перегрузки из Base в Derived скрываются целиком, если в наследнике появился метод с тем же именем.
И дальше d.Set("hello") уже ищет только среди перегрузок Derived.
А const char* в bool конвертируется просто замечательно.
И по нашей любимой традиции - ни одного ворнинга ни в одном из компиляторов. | 1 704 |
