Товарищи, а вы откуда в таком количестве подписываетесь сегодня? Кого благодарить за ссылку?
😁25👌1
Сегодня у нас поиски глубинного смысла в С++ на основе примеров, которые подсказывают подписчики.
В чем цимес. Лично мне в С++ не всегда понятно, что должно быть "нормальным поведением по-умолчанию", а что "нужно прописать явно".
Давайте посмотрим вот сюда:
Мы создали структуру, прописали, что ее можно сравнивать (по дефолту), и сравнили.
А теперь давайте унаследуем такую же структуру от пустой структуры
Мы не можем скомпилировать этот код, потому что оператор сравнения "по-умолчанию" не создается, пока мы не объявим явным образом оператор сравнения для пустой структуры.
Другими словами, нам надо явно писать что-то такое:
И вот не понятно, толи все логично, и я придераюсь. Толи правда неплохо бы генерировать операторы сравнения для пустых структур, а явно прописовать требовать только когда мы хотим их явно запретить. Я не знаю, я не понимаю...
В чем цимес. Лично мне в С++ не всегда понятно, что должно быть "нормальным поведением по-умолчанию", а что "нужно прописать явно".
Давайте посмотрим вот сюда:
#include <iostream>
struct Good {
int i;
bool operator==(const Good&) const = default;
};
int main() {
Good good1{1}, good2{2};
std::cout << (good1 == good2) << std::endl;
return 0;
}
Мы создали структуру, прописали, что ее можно сравнивать (по дефолту), и сравнили.
А теперь давайте унаследуем такую же структуру от пустой структуры
#include <iostream>
struct Empty {
// bool operator==(const Empty&) const = default;
};
struct Bad : Empty {
int i;
bool operator==(const Bad&) const = default;
/* error:
constexpr bool Bad::operator==(const Bad&) const'
is implicitly deleted because the default
definition would be ill-formed
*/
};
static_assert(sizeof(Good) == sizeof(Bad));
int main() {
Bad bad1{{}, 1}, bad2{{}, 2};
std::cout << (bad1 == bad2) << std::endl;
return 0;
}
Мы не можем скомпилировать этот код, потому что оператор сравнения "по-умолчанию" не создается, пока мы не объявим явным образом оператор сравнения для пустой структуры.
Другими словами, нам надо явно писать что-то такое:
struct Empty {
bool operator==(const Empty&) const {
return true;
}
};
И вот не понятно, толи все логично, и я придераюсь. Толи правда неплохо бы генерировать операторы сравнения для пустых структур, а явно прописовать требовать только когда мы хотим их явно запретить. Я не знаю, я не понимаю...
godbolt.org
Compiler Explorer - C++
struct Good {
int i;
bool operator==(const Good&) const = default;
};
struct Empty {
// bool operator==(const Empty&) const = default;
};
struct Bad : Empty {
int i;
bool operator==(const Bad&) const = default;
};
static_assert(sizeof(Good)…
int i;
bool operator==(const Good&) const = default;
};
struct Empty {
// bool operator==(const Empty&) const = default;
};
struct Bad : Empty {
int i;
bool operator==(const Bad&) const = default;
};
static_assert(sizeof(Good)…
🤔7😁3❤2
И снова спасибо подписчикам за отборный контент.
Оказывается, в С++ можно объявить оператор каста к... void.
Да, он ворнингом скажет, что ты никогда не сможешь его использовать, но объявить, и даже скомпилировать - это запросто.
Но самая большая радость... При должном желании и упорстве, вопреки предупреждениям ворнинга, вы таки сможете это запустить:
И вот это уже вообще взрыв мозга! 🤯
Оказывается, в С++ можно объявить оператор каста к... void.
struct X {
// warning: Conversion function converting 'X' to 'void' will never be used
operator void() { std::cerr << "void\n"; }
};
Да, он ворнингом скажет, что ты никогда не сможешь его использовать, но объявить, и даже скомпилировать - это запросто.
Но самая большая радость... При должном желании и упорстве, вопреки предупреждениям ворнинга, вы таки сможете это запустить:
#include <iostream>
struct X {
// warning: Conversion function converting 'X' to 'void' will never be used
operator void() { std::cerr << "void\n"; }
};
int main(int argc, char *argv[]) {
X x;
(void)x; // no
static_cast<void>(x); // no
x.operator void(); // YES!!!
return 0;
}
И вот это уже вообще взрыв мозга! 🤯
godbolt.org
Compiler Explorer - C++
struct X {
// warning: Conversion function converting 'X' to 'void' will never be used
operator void() { std::cerr << "void\n"; }
};
int main(int argc, char *argv[]) {
X x;
(void)x; // no
static_cast<void>(x); // no
x.operator void();…
// warning: Conversion function converting 'X' to 'void' will never be used
operator void() { std::cerr << "void\n"; }
};
int main(int argc, char *argv[]) {
X x;
(void)x; // no
static_cast<void>(x); // no
x.operator void();…
😁21💊11🔥1
Этот пост был в очереди где-то уже на май, но чет у меня настроение лирическое, публикую сейчас.
Мне тут мой дорогой друг присоветовал (подписывайтесь на его boosty !)
подрезать из одного доклада примерчики для этого канала. И был абсолютно прав!
Я еще не досмотрел доклад, но в нем даже классические и всем известные примеры можно показать каким-то новым способом, который выглядит нелепо и комично:
Думаю, что на тот момент, когда вы это читаете, я уже доклад досмотрел, и поставил в отложку еще некоторое количество примеров. Поэтому если вам не терпится, и хочется спойлеров - смотрите доклад из сообщения.
подрезать из одного доклада примерчики для этого канала. И был абсолютно прав!
Я еще не досмотрел доклад, но в нем даже классические и всем известные примеры можно показать каким-то новым способом, который выглядит нелепо и комично:
int main() {
static_assert((double)(0.3) == 0.29999999999999998);
static_assert((double)(0.3) != ((double)(0.1) + (double)(0.2)));
}
Думаю, что на тот момент, когда вы это читаете, я уже доклад досмотрел, и поставил в отложку еще некоторое количество примеров. Поэтому если вам не терпится, и хочется спойлеров - смотрите доклад из сообщения.
Telegram
this->notes.
#cpp
Сегодня только один доклад. Зато какой!
Using Floating-point in C++: What Works, What Breaks, and Why. Egor Suvorov.
Егор рассказывает про устройство чисел с плавающей запятой. Миллион возможных проблем с ними в рамках спецификации, корнеров и C++.…
Сегодня только один доклад. Зато какой!
Using Floating-point in C++: What Works, What Breaks, and Why. Egor Suvorov.
Егор рассказывает про устройство чисел с плавающей запятой. Миллион возможных проблем с ними в рамках спецификации, корнеров и C++.…
👍4🥰3❤1😁1
Сегодня разбираем вот такой рабочий пример. (Рабочий в том смысле, что найден на работе).
Берем вот такой код:
Внимательно на него смотрим. Потом смотрим еще внимательнее.
Не видим проблемы.
Смотрим еще раз, и опять не видим проблемы.
А потом отправляем его на компиляцию, и получаем ошибку:
У нас нет инстанса хеша для строки.
😣😣😣😣😣😣
Как это исправляется? Правильно, заголовок строки надо обязательно ставить выше заголовка мапы:
Точнее на самом деле, разумеется, важен не порядок заголовков (хотя там отдельный геморой. И в большинстве codestyle-ах порядок заголовков указывается, хотя на моей памяти "правильный" порядок поменялся ровно на противоположный).
Нужно чтобы string была указана до объявления мапы.
Скомпилируется:
Не скомпилируется:
Скомпилируется:
Скомпилируется:
И это какая-то лютая хрень, которую не найти не исправить.
Как вообще оно так вышло?
Берем вот такой код:
// hpp
#include <unordered_map>
struct Foo {
int x;
int y;
};
struct Bar{
std::unordered_map<std::string, Foo> xx;
Foo& ForY(const std::string& v);
};
// cpp
#include <string>
Foo& Bar::ForY(const std::string& v) {
return xx[v];
}
int main() {}
Внимательно на него смотрим. Потом смотрим еще внимательнее.
Не видим проблемы.
Смотрим еще раз, и опять не видим проблемы.
А потом отправляем его на компиляцию, и получаем ошибку:
/cefs/aa/aad5f6fdba80b622f643f9a5_clang-trunk-20260313/bin/../include/c++/v1/unordered_map:657:74: error: type 'const std::hash<std::string>' does not provide a call operator
657 | _LIBCPP_HIDE_FROM_ABI size_t operator()(const _Cp& __x) const { return __hash_(__x.first); }
У нас нет инстанса хеша для строки.
😣😣😣😣😣😣
Как это исправляется? Правильно, заголовок строки надо обязательно ставить выше заголовка мапы:
// hpp
#include <string>
#include <unordered_map>
struct Foo {
int x;
int y;
};
struct Bar{
std::unordered_map<std::string, Foo> xx;
Foo& ForY(const std::string& v);
};
// cpp
Foo& Bar::ForY(const std::string& v) {
return xx[v];
}
int main() {}
Точнее на самом деле, разумеется, важен не порядок заголовков (хотя там отдельный геморой. И в большинстве codestyle-ах порядок заголовков указывается, хотя на моей памяти "правильный" порядок поменялся ровно на противоположный).
Нужно чтобы string была указана до объявления мапы.
Скомпилируется:
#include <string>
struct Bar{
std::unordered_map<std::string, Foo> xx;
Foo& ForY(const std::string& v);
};
Не скомпилируется:
struct Bar{
std::unordered_map<std::string, Foo> xx;
Foo& ForY(const std::string& v);
};
#include <string>
Скомпилируется:
struct Bar{
std::unordered_map<std::string, Foo> xx;
Foo& ForY(const std::string& v);
};
#include <string>
// Foo& Bar::ForY(const std::string& v) {
// return xx[v];
// }
Скомпилируется:
struct Bar{
std::unordered_map<std::string, Foo> xx;
Foo& ForY(const std::string& v);
};
#include <string>
Foo& Bar::ForY(const std::string& ) {
return xx.begin()->second;
}
И это какая-то лютая хрень, которую не найти не исправить.
Как вообще оно так вышло?
godbolt.org
Compiler Explorer - C++ (x86-64 clang (trunk))
// hpp
struct Foo {
int x;
int y;
};
struct Bar{
std::unordered_map<std::string, Foo> xx;
Foo& ForY(const std::string& v);
};
// cpp
Foo& Bar::ForY(const std::string& v) {
return xx[v];
}
int main() {}
struct Foo {
int x;
int y;
};
struct Bar{
std::unordered_map<std::string, Foo> xx;
Foo& ForY(const std::string& v);
};
// cpp
Foo& Bar::ForY(const std::string& v) {
return xx[v];
}
int main() {}
🔥9❤3
Некоторые вещи о С++ я знаю натурально против своей воли.
Давайте возьмем вот такие флаги компиляции
Для icc сделаем
Стартовый пример, который хотел показать.
Ни одного ворнинга на компиляторах не выдает.
Что в этом примере:
Самая обычная функция, которая нигде в коде не вызывается. Внутри функции
Внимание, вопрос! А можно ли как-то вызвать эту лямбду?
Оказывается да, и нам в этом помогает вот такая строчка:
Что за классы такие А и В? А вот как они объявляются:
И вуаля, теперь мы можем вызвать эту функцию вот таким кодом:
Много раз повторив добьемся того же эффекта. И ни одного ворнинга.
Да, объявив лямбду вот так:
Получим веселую ошибку компиляции:
(Да, я просто добавил `[&]`).
И я изначально, встретив нечно похожее, шел по пути усложнения кода. Потому что хотел избавиться от всех ворнингов, а, например, закомментрируем template в объявлении функции get, и хотябы gcc начнет сыпать хоть какими-то ворнингами:
А
Но как оказалось, я был не прав, и идти надо по пути упрощения. Потому что
тоже прекрасно работает.
Вот ей богу, я эту грязь знать не хотел. 🤢
Давайте возьмем вот такие флаги компиляции
--std=c++2c -O2 -pedantic -Wall -Wextra -fsanitize=address -fsanitize=undefined
Для icc сделаем
-std=c++20, потому что 2c он не знает. Стартовый пример, который хотел показать.
Ни одного ворнинга на компиляторах не выдает.
Что в этом примере:
void foo() { // never called
if constexpr(false) { // never true
if (false) { // never true
constexpr auto call = [](auto arg) {
std::printf("called %d", arg);
};
void(B<A<tag>, decltype(call)>{});
}
}
}
Самая обычная функция, которая нигде в коде не вызывается. Внутри функции
if constexpr (false) который какбы тоже не должен никогда вызваться. Я бы вообще ожидал что блок внутри выкенется из компиляции. Внутри этого блока if (false). Внутри котого лямбда (причем с аргументом и локальной переменной). Внимание, вопрос! А можно ли как-то вызвать эту лямбду?
Оказывается да, и нам в этом помогает вот такая строчка:
void(B<A<tag>, decltype(call)>{});
Что за классы такие А и В? А вот как они объявляются:
class tag;
template<class>
struct A {
template<class>
friend constexpr auto get(A);
};
template<class K, class V>
struct B {
template<class>
friend constexpr auto get(K) { return V{}; }
};
И вуаля, теперь мы можем вызвать эту функцию вот таким кодом:
int main() {
get<tag>(A<tag>{})(42);
}
Много раз повторив добьемся того же эффекта. И ни одного ворнинга.
Да, объявив лямбду вот так:
constexpr auto call = [&](auto arg) {
std::printf("called %d", arg);
};
Получим веселую ошибку компиляции:
note: a lambda closure type has a deleted default constructor
(Да, я просто добавил `[&]`).
И я изначально, встретив нечно похожее, шел по пути усложнения кода. Потому что хотел избавиться от всех ворнингов, а, например, закомментрируем template в объявлении функции get, и хотябы gcc начнет сыпать хоть какими-то ворнингами:
template<class>
struct A {
// template<class>
friend constexpr auto get(A);
};
template<class K, class V>
struct B {
// template<class>
friend constexpr auto get(K) { return V{}; }
};
warning: friend declaration 'constexpr auto get(A< <template-parameter-1-1> >)' declares a non-template function
А
icc так вообще перестанет собирать код:
internal error: assertion failed at: "func_def.c", line 1915 in scan_function_body
get(A<tag>{})(42);
Но как оказалось, я был не прав, и идти надо по пути упрощения. Потому что
#include <cstdio>
struct A {
friend auto get(A);
};
template<class V>
struct B {
friend auto get(A) { return V{}; }
};
void foo() { // never called
if constexpr(false) { // never true
if (false) { // never true
constexpr auto call = [](auto arg) {
std::printf("called %d", arg);
};
void(B<decltype(call)>{});
}
}
}
int main() {
get(A{})(42);
}
тоже прекрасно работает.
Вот ей богу, я эту грязь знать не хотел. 🤢
🤯16❤4🔥2👍1💊1
Итак, у нас, согласно cppreference, c 11-ого стандарта есть набор целочисленных типов типа:
Ну давайте поиграемся.
Что будет выведено вот на это:
Правильный ответ:
```
int16_t:
0
48
65
```
А вот на это?
Правильный ответ:
```
int64_t:
0
48
65
```
А вот на это?
Правильный ответ:
```
int8_t:
0
A
```
А все почему?
Потому что идите все нахер, int8_t - это char.
Особенно это приятно, когда у вас из логов пропадает что-то такое:
Ну вот и нахрен так жить?
int8_t
int16_t
int32_t
int64_t
signed integer type with width of exactly 8, 16, 32 and 64 bits respectively
with no padding bits and using 2's complement for negative values
(provided if and only if the implementation directly supports the type)
Ну давайте поиграемся.
Что будет выведено вот на это:
std::cout << "int16_t: \n"
<< static_cast<int16_t>(00) << '\n'
<< static_cast<int16_t>(48) << '\n'
<< static_cast<int16_t>(65) << '\n'
<< std::endl;
Правильный ответ:
int16_t:
0
48
65
```
А вот на это?
std::cout << "int64_t: \n"
<< static_cast<int64_t>(00) << '\n'
<< static_cast<int64_t>(48) << '\n'
<< static_cast<int64_t>(65) << '\n'
<< std::endl;
Правильный ответ:
int64_t:
0
48
65
```
А вот на это?
std::cout << "int8_t: \n"
<< static_cast<int8_t>(00) << '\n'
<< static_cast<int8_t>(48) << '\n'
<< static_cast<int8_t>(65) << '\n'
<< std::endl;
Правильный ответ:
```
int8_t:
0
A
```
А все почему?
Особенно это приятно, когда у вас из логов пропадает что-то такое:
enum class Direction : int8_t {
LEFT, RIGHT, UP, DOWN
};
// ...
std::cout << static_cast<int8_t>(Direction::LEFT)
<< std::endl;
using Int = std::underlying_type_t<Direction>;
std::cout << static_cast<Int>(Direction::LEFT)
<< std::endl;
Ну вот и нахрен так жить?
godbolt.org
Compiler Explorer - C++
enum class Direction : int8_t {
LEFT, RIGHT, UP, DOWN
};
int main() {
std::cout << "int16_t: \n"
<< static_cast<int16_t>(00) << '\n'
<< static_cast<int16_t>(48) << '\n'
<< static_cast<int16_t>(65) << '\n'…
LEFT, RIGHT, UP, DOWN
};
int main() {
std::cout << "int16_t: \n"
<< static_cast<int16_t>(00) << '\n'
<< static_cast<int16_t>(48) << '\n'
<< static_cast<int16_t>(65) << '\n'…
😁22👍4🔥1💊1
Прекрасный и интуитивный auto.
Давайте возьмем вот такой код для старта.
Давайте попробуем угадать, какого типа будут переменные a и b?
Это прекрасное:
Тоесть при копии у нас теряется константность. А при получении ссылки не теряется.
И если разобраться.... Это абсолютно логично!!!
Но блин, когда ты только только глядишь - это сначала выбивает тебя немного в ступор.
А что делать, если мы хотим что-то менее логичное, но более интуитивное?
А что-то такое:
Давайте возьмем вот такой код для старта.
int main() {
const int x = 42;
auto a = x;
auto& b = x;
}Давайте попробуем угадать, какого типа будут переменные a и b?
decltype(a)
decltype(b)
Это прекрасное:
static_assert(std::is_same_v<decltype(a), int>);
static_assert(std::is_same_v<decltype(b), const int&>);
Тоесть при копии у нас теряется константность. А при получении ссылки не теряется.
И если разобраться.... Это абсолютно логично!!!
Но блин, когда ты только только глядишь - это сначала выбивает тебя немного в ступор.
А что делать, если мы хотим что-то менее логичное, но более интуитивное?
А что-то такое:
#include <iostream>
#include <type_traits>
int main() {
const int x = 42;
decltype(auto) a = x;
auto& b = x;
// ...
}
godbolt.org
Compiler Explorer - C++
int main() {
const int x = 42;
auto a = x;
auto& b = x;
static_assert(std::is_same_v<decltype(a), int>);
static_assert(std::is_same_v<decltype(b), const int&>);
}
const int x = 42;
auto a = x;
auto& b = x;
static_assert(std::is_same_v<decltype(a), int>);
static_assert(std::is_same_v<decltype(b), const int&>);
}
👍10❤2
Маленька классика на этой неделе.
Что выведет вот этот код?
Да, все верно:
```
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';
}
🔥19😁10❤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 !)
подрезать из одного доклада примерчики для этого канала. И был абсолютно прав!
Я еще не досмотрел доклад, но в нем даже классические и всем известные примеры можно показать каким-то…
подрезать из одного доклада примерчики для этого канала. И был абсолютно прав!
Я еще не досмотрел доклад, но в нем даже классические и всем известные примеры можно показать каким-то…
😱11🤯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👍2❤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😁9☃2❤1
Сижу на CppRussia.
Пока тут каждый второй слайд первого же доклада - кандидат на пост сюда....
Пока тут каждый второй слайд первого же доклада - кандидат на пост сюда....
😁54💯7🔥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🤯10❤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>…
❤26🍌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:>;
%>
😁24🤯11😐2