Внимание, шутка!!
Умножение в С++ некоммутативно.
Вот пример:
Выведет 5 только на первую строчку. А на вторую нет.
Живите с этим.
Разумеется, тут проблема в том, что в С++ числа с плавающей запятой пишутся с точкой. Запятая - это отдельныйоператор С++, который выводит второе значение.
Молодцы, что разгадали этот простенький паззл 🎁
К слову, clang выводит ворнинг на это. А gcc только при включенном Wall.
Умножение в С++ некоммутативно.
Вот пример:
#include <iostream>
int main() {
std::cout << (2,0 * 2,5) << std::endl; // 5
std::cout << (2,5 * 2,0) << std::endl; // ???
return 0;
}
Выведет 5 только на первую строчку. А на вторую нет.
Живите с этим.
Разумеется, тут проблема в том, что в С++ числа с плавающей запятой пишутся с точкой. Запятая - это отдельный
Молодцы, что разгадали этот простенький паззл 🎁
К слову,
godbolt.org
Compiler Explorer - C++
int main() {
std::cout << (2,0 * 2,5) << std::endl;
std::cout << (2,5 * 2,0) << std::endl;
return 0;
}
std::cout << (2,0 * 2,5) << std::endl;
std::cout << (2,5 * 2,0) << std::endl;
return 0;
}
😁8❤7🌚5🤓2
Немного про новые стандарты: когда я показываю какую-то упячку, часто мне говорят "просто не пользуйся вот этим...".
Например, "не пользуйся new/delete, пользуйся умными указателями".
Или "не пользуйся конструкторами с круглыми скобками, пользуйся {}. И порядок зафиксирует и от кучи проблем избавит.".
Увы, новые конструкции просто создают другое подмножество проблем.
Вот к примеру, что выведет вот такой код?
Правильно, он выведет
Что характерно, вот такая строчка
Не скомпилируется. Потому что у строки нет конструктора от char. Точнее есть, но там надо развлекаться веселее.
Это будет
Так или иначе, но у нас новые конструкции просто создают новые проблемы.
Ну и самая большая проблема, когда эти вещи вызываются неявно.
Человек, который прислал мне похожий пример, утверждает, что он пришел из продакшн кода.
Что тут происходит? double кастится к int, int кастится к char, char кастится к string, в мапу записывается буква
Например, "не пользуйся new/delete, пользуйся умными указателями".
Или "не пользуйся конструкторами с круглыми скобками, пользуйся {}. И порядок зафиксирует и от кучи проблем избавит.".
Увы, новые конструкции просто создают другое подмножество проблем.
Вот к примеру, что выведет вот такой код?
#include <iostream>
int main() {
auto a = std::string{48};
std::cout << a << std::endl;
}
Правильно, он выведет
0. Потому что идет неявное преобразование инта к чару. И chr(48) == '0'.Что характерно, вот такая строчка
auto b = std::string(48); // error: no matching function for call to
Не скомпилируется. Потому что у строки нет конструктора от char. Точнее есть, но там надо развлекаться веселее.
#include <iostream>
int main() {
auto b = std::string(48, 48.0);
std::cout << b << std::endl;
}
Это будет
000000000000000000000000000000000000000000000000
Так или иначе, но у нас новые конструкции просто создают новые проблемы.
Ну и самая большая проблема, когда эти вещи вызываются неявно.
Человек, который прислал мне похожий пример, утверждает, что он пришел из продакшн кода.
#include <iostream>
#include <unordered_map>
int main() {
std::unordered_map<std::string, std::string> m;
double i = 65.5;
m["hello"] = i;
std::cout << m.at("hello");
return 0;
}
Что тут происходит? double кастится к int, int кастится к char, char кастится к string, в мапу записывается буква
A. Прекрасно!godbolt.org
Compiler Explorer - C++
int main() {
auto a = std::string{48};
std::cout << a << std::endl;
// auto b = std::string(48); // error: no matching function for call to
}
auto a = std::string{48};
std::cout << a << std::endl;
// auto b = std::string(48); // error: no matching function for call to
}
🥴14🔥3😢3❤2🤩1
implicit конструирование типов вообще сплошная головная боль.
Но мой любимый лефтикус в одном из своих докладов (чет не помню, в каком, но в последние пару лет) поделился синтаксическим трюком, как их запретить.
Вот у нас есть пример.
Мы хотим как-то запускать функцию
У нас, казалось бы, есть защита от неправильного порядка аргументов.
Но есть проблема. Оба типа могут неявно конструироваться от строки. Вот эти обе строчки сработают:
Есть решение!Нам поможет вот такая конструкция:
```cpp
void f(const auto&, const auto&) = delete;
void f(const Path& path, const std::string& mode) {
std::cout << "Path: " << path.path << std::endl;
std::cout << "Mode: " << mode << std::endl;
}
```
Что мы теперь получим при вызове функций от голых строк?
```
<source>:18:5: error: call to deleted function 'f'
18 | f("/etc/tmp", "rw");
```
Прекрасный трюк, очень мне нравится.
Но мой любимый лефтикус в одном из своих докладов (чет не помню, в каком, но в последние пару лет) поделился синтаксическим трюком, как их запретить.
Вот у нас есть пример.
struct Path {
Path(const std::string& p) : path(p) {}
Path(const char* p) : path(p) {}
std::string path;
};
void f(const Path& path, const std::string& mode) {
std::cout << "Path: " << path.path << std::endl;
std::cout << "Mode: " << mode << std::endl;
}
Мы хотим как-то запускать функцию
f.У нас, казалось бы, есть защита от неправильного порядка аргументов.
f(Path("/etc/tmp"), std::string{"rw"});
// f(std::string{"rw"}, Path("/etc/tmp"));
// error: no matching function for call to 'f'
Но есть проблема. Оба типа могут неявно конструироваться от строки. Вот эти обе строчки сработают:
f("/etc/tmp", "rw");
f("rw", "/etc/tmp");
Есть решение!
```cpp
void f(const auto&, const auto&) = delete;
void f(const Path& path, const std::string& mode) {
std::cout << "Path: " << path.path << std::endl;
std::cout << "Mode: " << mode << std::endl;
}
```
Что мы теперь получим при вызове функций от голых строк?
```
<source>:18:5: error: call to deleted function 'f'
18 | f("/etc/tmp", "rw");
```
Прекрасный трюк, очень мне нравится.
🔥15🤔3
Ну а как еще можно было назвать канал, как не "Дурка"?
Ну сам факт, что вот такой код компилируется, запускается, а еще и не падает, и выводит 0!!!
Нет, конечно, понятно даже почему, если разобрать поднаготную устройства функций-членов класса. Но блииииин........
Ну сам факт, что вот такой код компилируется, запускается, а еще и не падает, и выводит 0!!!
#include <iostream>
class Test {
public:
void test() {
std::cout << this << std::endl;
}
};
Test & create()
{
return *((Test*)NULL);
}
int main()
{
Test &t = create();
t.test();
}
Нет, конечно, понятно даже почему, если разобрать поднаготную устройства функций-членов класса. Но блииииин........
godbolt.org
Compiler Explorer - C++
class Test {
public:
void test() {
std::cout << this << std::endl;
}
};
Test & create()
{
return *((Test*)NULL);
}
int main()
{
Test &t = create();
t.test();
}
public:
void test() {
std::cout << this << std::endl;
}
};
Test & create()
{
return *((Test*)NULL);
}
int main()
{
Test &t = create();
t.test();
}
💊9😨3❤1👍1👎1😢1
Неожиданный пример гонки в С++.
Возьмем такой код.
Что мы тут видим?
Класс Task с чисто виртуальной функцией. Интерфейс, типичный интерфейс.
Промежуточный класс AutorunTask, который в конструкторе запускает в конструкторе виртуальный метод, и в деструкторе дожидается его окончания.
И в конце концов реализация виртуальной функции, которая и вызывается.
И в таком виде код работает, и выводит
Но зачем мы поставили sleep в конце? Дело в том, что если его закомментировать, то код упадет с ошибкой.
Итак. Давайте разбираться.
Можно ли вообще в конструкторе/деструкторе использовать виртуальные функции?
Смотрим
paragraph 4 [ISO/IEC 14882-2014]
Тоесть, можно.
Что же они запускают? Ведь функция в классе-наследнике может обращаться к несконструированному классу?
На самом деле, в конструкторе и деструкторе вызывается реализация "текущего" класса. Смотрим пример.
Это код выводит
конструктор первого класса выведет A. Второго B.
А как это достигается? На самом деле, в программе переписывается указатель в таблице виртуальных вызовов.
Ооооо, вот тут и возникает гонка.
Вот тут перед непосредственным вызовом деструктора устанавливается другой указатель в таблице виртальных вызовов.
А вызов внутри лямбды происходит не конкретной функции, а по указателю из таблицы виртуальных вызовов.
И дальше происходит гонка. Если деструктор вызовется раньше, чем запустится поток (версия без `sleep`), то вызовется чистовиртуальный метод, что приведет к ошибке.
А если деструктор вызывается позже (версия со `sleep`), то все отработает штатно.
Добро пожаловать в красивые примеры нашей любимой дурки. 🤡
Возьмем такой код.
struct Task {
virtual ~Task() = default;
virtual void run() = 0;
};
class AutorunTask : public Task {
public:
AutorunTask() : t_([this]() {
this->run();
}) {}
~AutorunTask() { t_.join(); }
private:
std::thread t_;
};
struct Impl : public AutorunTask {
void run() override {
puts("The string");
}
};
int main(){
Impl impl;
std::this_thread::sleep_for(1ms);
}
Что мы тут видим?
Класс Task с чисто виртуальной функцией. Интерфейс, типичный интерфейс.
Промежуточный класс AutorunTask, который в конструкторе запускает в конструкторе виртуальный метод, и в деструкторе дожидается его окончания.
И в конце концов реализация виртуальной функции, которая и вызывается.
И в таком виде код работает, и выводит
Program returned: 0
The string
Но зачем мы поставили sleep в конце? Дело в том, что если его закомментировать, то код упадет с ошибкой.
Program returned: 139
pure virtual method called
terminate called without an active exception
Program terminated with signal: SIGSEGV
Итак. Давайте разбираться.
Можно ли вообще в конструкторе/деструкторе использовать виртуальные функции?
Смотрим
paragraph 4 [ISO/IEC 14882-2014]
Member functions, including virtual functions, can be called during construction or destruction. When a virtual function is called directly or indirectly from a constructor or from a destructor, including during the construction or destruction of the class’s non-static data members, and the object to which the call applies is the object (call it x) under construction or destruction, the function called is the final overrider in the constructor’s or destructor’s class and not one overriding it in a more-derived class. If the virtual function call uses an explicit class member access and the object expression refers to the complete object of x or one of that object’s base class subobjects but not x or one of its base class subobjects, the behavior is undefined.
Тоесть, можно.
Что же они запускают? Ведь функция в классе-наследнике может обращаться к несконструированному классу?
На самом деле, в конструкторе и деструкторе вызывается реализация "текущего" класса. Смотрим пример.
struct A {
virtual void f() {
puts("A");
}
A() { f(); }
};
struct B : public A {
public:
void f() override {
puts("B");
}
B() { f(); }
};
int main(){
B b;
}
Это код выводит
Program returned: 0
A
B
конструктор первого класса выведет A. Второго B.
А как это достигается? На самом деле, в программе переписывается указатель в таблице виртуальных вызовов.
Ооооо, вот тут и возникает гонка.
AutorunTask::~AutorunTask() [base object destructor]:
push rbx
mov rbx, rdi
lea rax, [rip + vtable for AutorunTask+16]
mov qword ptr [rdi], rax
add rbx, 8
mov rdi, rbx
call std::thread::join()@PLT
Вот тут перед непосредственным вызовом деструктора устанавливается другой указатель в таблице виртальных вызовов.
vtable for AutorunTask:
.quad 0
.quad typeinfo for AutorunTask
.quad AutorunTask::~AutorunTask() [base object destructor]
.quad AutorunTask::~AutorunTask() [deleting destructor]
.quad __cxa_pure_virtual
А вызов внутри лямбды происходит не конкретной функции, а по указателю из таблицы виртуальных вызовов.
std::thread::_State_impl<std::thread::_Invoker<std::tuple<AutorunTask::AutorunTask()::'lambda'()>>>::_M_run() [complete object constructor]:
mov rdi, qword ptr [rdi + 8]
mov rax, qword ptr [rdi]
jmp qword ptr [rax + 16]
И дальше происходит гонка. Если деструктор вызовется раньше, чем запустится поток (версия без `sleep`), то вызовется чистовиртуальный метод, что приведет к ошибке.
А если деструктор вызывается позже (версия со `sleep`), то все отработает штатно.
Добро пожаловать в красивые примеры нашей любимой дурки. 🤡
godbolt.org
Compiler Explorer - C++
using namespace std::chrono_literals;
struct Task {
virtual ~Task() = default;
virtual void run() = 0;
};
class AutorunTask : public Task {
public:
AutorunTask() : t_([this]() {
this->run();
}) {}
~AutorunTask() { t_.join();…
struct Task {
virtual ~Task() = default;
virtual void run() = 0;
};
class AutorunTask : public Task {
public:
AutorunTask() : t_([this]() {
this->run();
}) {}
~AutorunTask() { t_.join();…
❤11🤯11🫡5👍4
Иногда нам приходится использовать самое страшное и отвратительное что досталось С++ по наследству: препроцессор.
По разным причинам - или средствами языка нельзя что-то емко выразить (как в либах Unreal Engine), или для производительности (иногда голый код лучше оптимизируется чем функции, и макросы пишутся вместо функций под комментарием "счастливого дебага, тварь"), иногда просто по дурости (как в QT).
И там можно выстрелить себе в ногу самым неожиданным образом.
Вот простой макрос:
Что тут не так?
Да много чего.
Вот такой пример:
Вообще это преобразуется в
И это явно не то, что мы хотели. Мы хотели тут ошибку компиляции на том, что "пытаемся инкрементить lvalue", а вместо этого простой ворнинг про "неиспользуемый результат".
Но это полбеды, тут даже идейно макрос использован неверно.
Какой-нибудь вот такой пример:
Разумно выведет
а clang даже со включенным Wall не выдаст ни одного ворнинга.
И это далеко не все проблемы. Когда-то давно, когда мне надо было писать макросы, мне к задачам выдали 18-страничный гайд "как правильно писать макросы", согласно которому единственно верным способом написать такой макрос будет
Не используйте макросы без крайней необходимости. А если используете - найдите тот гайд, и пришлите его мне (я его потерял и очень об этом жалею 🙁 )
По разным причинам - или средствами языка нельзя что-то емко выразить (как в либах Unreal Engine), или для производительности (иногда голый код лучше оптимизируется чем функции, и макросы пишутся вместо функций под комментарием "счастливого дебага, тварь"), иногда просто по дурости (как в QT).
И там можно выстрелить себе в ногу самым неожиданным образом.
Вот простой макрос:
#define INCREMENT_BOTH(x, y) x++; y++
Что тут не так?
Да много чего.
Вот такой пример:
int a = 0; int b = 0;
INCREMENT_BOTH(a+b, b);
Вообще это преобразуется в
a+b++; b++;
И это явно не то, что мы хотели. Мы хотели тут ошибку компиляции на том, что "пытаемся инкрементить lvalue", а вместо этого простой ворнинг про "неиспользуемый результат".
Но это полбеды, тут даже идейно макрос использован неверно.
Какой-нибудь вот такой пример:
int main() {
int a = 0; int b = 0;
for (const auto f: {true, false}) {
if (f)
INCREMENT_BOTH(a, b);
}
std::cout << "a: " << a << "\n"
<< "b: " << b << "\n"
<< std::endl;
return 0;
}
Разумно выведет
Program returned: 0
a: 1
b: 2
а clang даже со включенным Wall не выдаст ни одного ворнинга.
И это далеко не все проблемы. Когда-то давно, когда мне надо было писать макросы, мне к задачам выдали 18-страничный гайд "как правильно писать макросы", согласно которому единственно верным способом написать такой макрос будет
#define INCREMENT_BOTH(x, y) \
do { \
(x)++; \
(y)++; \
} while (0)
Не используйте макросы без крайней необходимости. А если используете - найдите тот гайд, и пришлите его мне (я его потерял и очень об этом жалею 🙁 )
godbolt.org
Compiler Explorer - C++
#define INCREMENT_BOTH(x, y) x++; y++
int main() {
int a = 0; int b = 0;
for (const auto f: {true, false}) {
if (f)
INCREMENT_BOTH(a, b);
}
std::cout << "a: " << a << "\n"
<< "b: " << b << "\n"
…
int main() {
int a = 0; int b = 0;
for (const auto f: {true, false}) {
if (f)
INCREMENT_BOTH(a, b);
}
std::cout << "a: " << a << "\n"
<< "b: " << b << "\n"
…
❤18
Еще одна абсолютно бесполезная, но унаследнованная штука в С++. Заголовок может заинклюдить сам себя.
Например вот такой заголовок:
Сработает совершенно нормально.
Ни ворнингов, ничего. А самое главное - как бы так передефайнить разные куски, чтобы на этом механизме устроить перебор? Ну, перебрать все комбинации из 5 дефайнов, и для каждого определить функцию? Было бы... Забавно?...
Например вот такой заголовок:
// megaheader.hpp
#ifndef MEGAHEADER_HPP
int foo() {
return 1;
}
#define MEGAHEADER_HPP
#include "megaheader.hpp"
#else
int bar() {
return 2;
}
#endif
Сработает совершенно нормально.
#include <iostream>
#include "megaheader.hpp"
int main() {
std::cout << "foo: " << foo() << std::endl;
std::cout << "bar: " << bar() << std::endl;
return 0;
}
foo: 1
bar: 2
Ни ворнингов, ничего. А самое главное - как бы так передефайнить разные куски, чтобы на этом механизме устроить перебор? Ну, перебрать все комбинации из 5 дефайнов, и для каждого определить функцию? Было бы... Забавно?...
🤔4😁2🥴2❤1👍1
(незапланированный пост)
А есть инфа, я что-то найти не могу.
Вот это в язык вводят?
А есть инфа, я что-то найти не могу.
Вот это в язык вводят?
template <class P, class Q>
auto dot_product(P p, Q q) {
// no indirection!
auto&& [...p_elems] = p;
auto&& [...q_elems] = q;
return (... + (p_elems * q_elems));
}
🤯6❤1
Причины, по которым этот канал называется "дурка".
Вот такой вот метод:
Компиляторы сожрут. И не выведут ни одного ворнинга при Wall.
Вот полный пример.
У меня все, увидимся в дурдоме
Вот такой вот метод:
void Suicide() {
delete this;
}
Компиляторы сожрут. И не выведут ни одного ворнинга при Wall.
Вот полный пример.
#include <iostream>
#include <vector>
struct S {
int a = 13;
std::vector<int> v{};
void Suicide() {
delete this;
}
};
int main(){
auto* s = new S();
s->Suicide();
return 0;
}
У меня все, увидимся в дурдоме
godbolt.org
Compiler Explorer - C++
struct S {
int a = 13;
std::vector<int> v{};
void Suicide() {
delete this;
}
};
int main(){
auto* s = new S();
s->Suicide();
return 0;
}
int a = 13;
std::vector<int> v{};
void Suicide() {
delete this;
}
};
int main(){
auto* s = new S();
s->Suicide();
return 0;
}
🤡10🤝5💊3🤷♂2❤1
Разберем вот такой вот пример. Что в нем не так?
И это какая-то иллюстрация Эффекта Манделлы, потому что часто на вопрос "что не так?" слышу простое, легкое для понимания, неправильное решение в духе
На самом деле, использование std::forward тут нежелательно.
Давайте разберем сразу 4 примера:
В первом примере все хорошо: тип зафиксирован в момент объявления инстанса класса
поэтому в объявлении функции U&& -> это rvalue reference. И поскольку rvalue reference is lvalue, мы должны его мувать. Если использовать std::forward (Как в примере номер 2), то ничего страшного не случиться, но будет выглядеть странно, и заставит ругаться clang-tidy. Возможно, будут какие-то гадости, которые я не смог воспроизвести.
В примерах 3-4 все наоборот. Тип U определяется на этапе вызова функции, и его мувать опасно: у нас аргумент функции - forwarding reference, а значит там может быть как rvalue, так и просто ссылка. Если вы муваете ссылку внутри функции, тот, кто ее вызывает, может внезапно обнаружить, что переданный объект, который он не мувал, изчез:
А потому пример 3 - неверный и потенциальный источник багов. А пример 4 - норм.
Итого, "корректные" решения - 1 и 4. И, как и положено в С++, примеры 2 и 3 нормально скомпилируются... И даже будут как-то работать, наверное, в большинстве случаев, но делать так не надо.
И вообще, читаем cpp core guidelines.
template<typename U>
struct Scheduler {
// ...
void Add(U&& callback) {
callback_ = std::move(callback);
}
// ...
U callback_;
};
И это какая-то иллюстрация Эффекта Манделлы, потому что часто на вопрос "что не так?" слышу простое, легкое для понимания, неправильное решение в духе
forwarding reference passed to std::move(), which may unexpectedly cause lvalues to be moved; use std::forward() instead
На самом деле, использование std::forward тут нежелательно.
Давайте разберем сразу 4 примера:
// 1
template<typename U>
struct Scheduler {
// ...
void Add(U&& callback) {
callback_ = std::move(callback);
}
// ...
U callback_;
};
// 2
template<typename U>
struct Scheduler {
// ...
void Add(U&& callback) {
callback_ = std::forward<U>(callback);
}
// ...
U callback_;
};
// 3
struct AnotherScheduler {
// ...
template<typename U>
void Add(U&& callback) {
callback_ = std::move(callback);
}
// ...
std::function<void()> callback_;
};
// 4
struct AnotherScheduler {
// ...
template<typename U>
void Add(U&& callback) {
callback_ = std::forward<U>(callback);
}
// ...
std::function<void()> callback_;
};
В первом примере все хорошо: тип зафиксирован в момент объявления инстанса класса
Scheduler<std::function<void()>> sh;
поэтому в объявлении функции U&& -> это rvalue reference. И поскольку rvalue reference is lvalue, мы должны его мувать. Если использовать std::forward (Как в примере номер 2), то ничего страшного не случиться, но будет выглядеть странно, и заставит ругаться clang-tidy. Возможно, будут какие-то гадости, которые я не смог воспроизвести.
В примерах 3-4 все наоборот. Тип U определяется на этапе вызова функции, и его мувать опасно: у нас аргумент функции - forwarding reference, а значит там может быть как rvalue, так и просто ссылка. Если вы муваете ссылку внутри функции, тот, кто ее вызывает, может внезапно обнаружить, что переданный объект, который он не мувал, изчез:
AnotherScheduler ash;
auto af = []() -> void {};
ash.Add(std::ref(af)); // moved here inside function
af(); // UB
А потому пример 3 - неверный и потенциальный источник багов. А пример 4 - норм.
Итого, "корректные" решения - 1 и 4. И, как и положено в С++, примеры 2 и 3 нормально скомпилируются... И даже будут как-то работать, наверное, в большинстве случаев, но делать так не надо.
И вообще, читаем cpp core guidelines.
BookMix.ru
Любая, даже самая сложная, проблема обязательно имеет простое, легкое для...
Любая, даже самая сложная, проблема обязательно имеет простое, легкое для понимания, неправильное решение.
👍16🔥3
Ну, к разного рода наркомании, что
Это одно и то же - все уже привыкли.
А как насчет инициализации массива с указанием ренжа индексов?
Типа:
Или инициализации единицы для вайтспейстов:
Это нормально без дополнительных выкрутасов работает на clang, правда, грустит и плюется ворнингами.
Эта штука называется Designated Initializers, и является частью ISO C99, а С++ пытается быть совместимым с С90, а 99 - оно так, по желанию.
На годболте у меня так и не получилось скомпилировать вот такой пример на gcc:
однако локально он нормально компилируется вот такой командной строкой:
Вот какие-то такие развлечения....
std::cout << a[42] << std::endl;
std::cout << 42[a] << std::endl;
Это одно и то же - все уже привыкли.
А как насчет инициализации массива с указанием ренжа индексов?
Типа:
static constexpr int a[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };
Или инициализации единицы для вайтспейстов:
int whitespace[256] =
{
[' '] = 1, ['\t'] = 1,
['\f'] = 1, ['\n'] = 1,
['\r'] = 1
};
Это нормально без дополнительных выкрутасов работает на clang, правда, грустит и плюется ворнингами.
Эта штука называется Designated Initializers, и является частью ISO C99, а С++ пытается быть совместимым с С90, а 99 - оно так, по желанию.
На годболте у меня так и не получилось скомпилировать вот такой пример на gcc:
#include <iostream>
int main() {
static constexpr int a[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };
std::cout << a[42] << std::endl;
std::cout << 42[a] << std::endl;
int whitespace[256] =
{
[' '] = 1, ['\t'] = 1,
['\f'] = 1, ['\n'] = 1,
['\r'] = 1
};
return 0;
}
однако локально он нормально компилируется вот такой командной строкой:
g++ --std=gnu++23 main.cpp
Вот какие-то такие развлечения....
godbolt.org
Compiler Explorer - C++
int main() {
static constexpr int a[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };
std::cout << a[42] << std::endl;
std::cout << 42[a] << std::endl;
int whitespace[256] =
{
[' '] = 1, ['\t'] = 1,
['\f'] = 1, ['\n']…
static constexpr int a[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };
std::cout << a[42] << std::endl;
std::cout << 42[a] << std::endl;
int whitespace[256] =
{
[' '] = 1, ['\t'] = 1,
['\f'] = 1, ['\n']…
👏11😁9🥴6❤1
Что бы вы сказали, найдя в коде вот такой кусок?
Ну, первое предположение было бы, что мы как-то хотим макросы заиспользовать, например:
И какую бы мы переменную не поставили - не будет ошибки компиляции из-за else перед if.
Но вот вам реализация foreach в QT для компиляторов от майкрософта (когда-то была, в последних версиях, очевидно, другая, и вообще не рекомендуется к использованию с версии Qt5.7).
И такую же реализацию можно найти в других проектах, наример, MAP_FOREACH в DynaMind Toolbox.
На кой черт оно тут?
А это следствие старого бага в VS 6: там внутри for цикла переменные не ограничивались в области видимости. Вот какой-то такой код нормально бы скомпилировался.
А почему не сделать тогда
Вот такую хрень в допотопные времена приходилось добавлять в код.
Как славно, что те времена давно прошли, и у нас на повестке новая дурка.
А вы в комментариях пишите (ну или загуглите), зачем нужен в этом макросе тернарный оператор:
if(0){}else
Ну, первое предположение было бы, что мы как-то хотим макросы заиспользовать, например:
if (0) {}
#if GCC
else if (gcc_comparison())
{
gcc_action();
}
#endif
#if CLANG
else if (clang_comparison())
{
claing_action();
}
#endif
И какую бы мы переменную не поставили - не будет ошибки компиляции из-за else перед if.
Но вот вам реализация foreach в QT для компиляторов от майкрософта (когда-то была, в последних версиях, очевидно, другая, и вообще не рекомендуется к использованию с версии Qt5.7).
И такую же реализацию можно найти в других проектах, наример, MAP_FOREACH в DynaMind Toolbox.
Note: Since Qt 5.7, the use of this macro is discouraged. Use C++11 range-based for, possibly with std::as_const(), as needed.
#define Q_FOREACH(variable, container) \
if (0) else
for (const QForeachContainerBase &_container_ = qForeachContainerNew(container); \
qForeachContainer(&_container_, true ? 0 : qForeachPointer(container))->condition(); \
++qForeachContainer(&_container_, true ? 0 : qForeachPointer(container))->i) \
for (variable = *qForeachContainer(&_container_, true ? 0 : qForeachPointer(container))->i; \
qForeachContainer(&_container_, true ? 0 : qForeachPointer(container))->brk; \
--qForeachContainer(&_container_, true ? 0 : qForeachPointer(container))->brk)
На кой черт оно тут?
А это следствие старого бага в VS 6: там внутри for цикла переменные не ограничивались в области видимости. Вот какой-то такой код нормально бы скомпилировался.
int main() {
for (int i = 0; i < 3; ++i) {
std::cout << i << std::endl;
}
std::cout << i << std::endl;
return 0;
}
А почему не сделать тогда
if(true) ? А потому что тогда бы сработало
Q_FOREACH(a, b)
else {
// ...
}
Вот такую хрень в допотопные времена приходилось добавлять в код.
Как славно, что те времена давно прошли, и у нас на повестке новая дурка.
А вы в комментариях пишите (ну или загуглите), зачем нужен в этом макросе тернарный оператор:
qForeachContainer(&_container_, true ? 0 : qForeachPointer(container))->condition();
GitHub
qtbase/src/corelib/global/qforeach.h at fb9f4444fa716f6aa93d3d13d3037b30c005d7a7 · qt/qtbase
Qt Base (Core, Gui, Widgets, Network, ...). Contribute to qt/qtbase development by creating an account on GitHub.
🥴5🔥3
Есть задачка, абсолютно практическая на самом деле.
Все знают про padding:
Размер структуры Foo 16, а у Bar, несмотря на то, что переменные тех же типов, размер 24. Потому что double размера 8 должен быть помещен по адресу памяти, кратному размеру самой переменной, поэтому место между переменными заполняется 8 фейковыми байтами.
Это классика, но вопрос не про это. Утверждение:
Задача - доказать что утверждение верно или привести контрпример.
Как я сказал, задача на самом деле практическая, подробности в следующем посте.
Все знают про padding:
#include <iostream>
struct Foo {
char a; // 1
int b; // 4
double c; // 8
};
struct Bar {
char a; // 1
double c; // 8
int b; // 4
};
int main() {
std::cout << sizeof(Foo) << '\n'; // 16
std::cout << sizeof(Bar) << '\n'; // 24
}
Размер структуры Foo 16, а у Bar, несмотря на то, что переменные тех же типов, размер 24. Потому что double размера 8 должен быть помещен по адресу памяти, кратному размеру самой переменной, поэтому место между переменными заполняется 8 фейковыми байтами.
Это классика, но вопрос не про это. Утверждение:
Размер структуры невозможно поменять, переставив переменные в ней В ОБРАТНОМ ПОРЯДКЕ
Задача - доказать что утверждение верно или привести контрпример.
Как я сказал, задача на самом деле практическая, подробности в следующем посте.
Сегодняшнее интересное меганаблюдение посвящено выбору стандартной библиотеки.
К примеру, мы можем скомпилировать программу с помощью clang двумя командами:
Это дело подключит разные реализации std. По идее, то, что регламентируется стандартом отличаться не должно (но тут, как мы знаем, всякое бывает)
Например, вот такой код
В обоих случаях выведет одинаковое:
Но если мы попробуем забуриться во внутреннее устройство, например, глянуть в каком порядке хранятся структурки внутри тупла:
То увидим внезапное
На libstdc++
На libc++
Тоесть libstdc++ хранит реальные значения в обратном порядке... Хотя get<0> вычисляет так, как мы того и ожидаем. И это может иметь всякие неожиданные последствия для, например, производительности.
Однако, см предыдущий пост, это как минимум не должно сказаться на размере структур данных. Потому что разворотом порядка членов padding не меняется.
Но если вы найдете пример, где все-таки меняется - пишите.
К примеру, мы можем скомпилировать программу с помощью clang двумя командами:
clang++ -O2 -std=c++20 -stdlib=libstdc++
clang++ -O2 -std=c++20 -stdlib=libc++
Это дело подключит разные реализации std. По идее, то, что регламентируется стандартом отличаться не должно (но тут, как мы знаем, всякое бывает)
Например, вот такой код
struct A {
int i;
bool operator<(const A& other) const {
return i < other.i;
}
};
struct B {
int i;
bool operator<(const B& other) const {
return i < other.i;
}
};
int main() {
const auto first = std::make_tuple(A{1}, B{4});
const auto second = std::make_tuple(A{2}, B{3});
std::cout << ((first < second) ? "less" : "more") << std::endl;
std::cout << ((std::get<0>(first) < std::get<0>(second)) ? "less" : "more") << std::endl;
В обоих случаях выведет одинаковое:
less
Но если мы попробуем забуриться во внутреннее устройство, например, глянуть в каком порядке хранятся структурки внутри тупла:
const int* const pf = reinterpret_cast<const int* const> (&first);
const int* const ps = reinterpret_cast<const int* const> (&second);
std::cout << pf[0] << ' ' << pf[1] << std::endl;
std::cout << ps[0] << ' ' << ps[1] << std::endl;
std::cout << (pf[0] < ps[0] ? "less" : "more") << std::endl;
То увидим внезапное
На libstdc++
3 2
more
На libc++
2 3
less
Тоесть libstdc++ хранит реальные значения в обратном порядке... Хотя get<0> вычисляет так, как мы того и ожидаем. И это может иметь всякие неожиданные последствия для, например, производительности.
Однако, см предыдущий пост, это как минимум не должно сказаться на размере структур данных. Потому что разворотом порядка членов padding не меняется.
Но если вы найдете пример, где все-таки меняется - пишите.
godbolt.org
Compiler Explorer - C++
struct A {
int i;
bool operator<(const A& other) const {
return i < other.i;
}
};
struct B {
int i;
bool operator<(const B& other) const {
return i < other.i;
}
};
int main() {
const auto first = std::make_tuple(A{1}…
int i;
bool operator<(const A& other) const {
return i < other.i;
}
};
struct B {
int i;
bool operator<(const B& other) const {
return i < other.i;
}
};
int main() {
const auto first = std::make_tuple(A{1}…
❤1
Тут в комментах к этом посту прислали восхитительный пример. Хорошо я сидел, когда это увидел.
Вот такое:
Админ канала ушел плакать в уголок и просить книжечку по GoLang. 😭😭😭😭
Вот такое:
struct Empty {};
struct Good : Empty {
int i;
char c;
};
struct Bad {
int i;
char c;
};
template <typename T>
struct S : T {
char c;
};
static_assert(sizeof(S<Good>) < sizeof(S<Bad>));
int main() {
return 0;
}
Админ канала ушел плакать в уголок и просить книжечку по GoLang. 😭😭😭😭
Telegram
C++: Хроники Дурки🚑
Сегодняшнее интересное меганаблюдение посвящено выбору стандартной библиотеки.
К примеру, мы можем скомпилировать программу с помощью clang двумя командами:
clang++ -O2 -std=c++20 -stdlib=libstdc++
clang++ -O2 -std=c++20 -stdlib=libc++
Это дело подключит…
К примеру, мы можем скомпилировать программу с помощью clang двумя командами:
clang++ -O2 -std=c++20 -stdlib=libstdc++
clang++ -O2 -std=c++20 -stdlib=libc++
Это дело подключит…
🔥10🥰3
Обычно когда спрашивают, что такое nullptr, получают в ответ, что это указатель равный нулю.
Более продвинутые говорят, что это зависит от имплементации, и приводят в пример какие-то embedded сценарии архитектур, до которых среднему программисту дела нет. Поэтому первая часть людей продолжает говорить, что в практическом поле nullptr - это ноль.
Но есть красивый пример, иллюстрирующий что это не так.
Природа заблуждения растет, как мне кажется, из проверки подстановкой указателя в if.
Ну тут все очевидно. Выведет вот это:
Указатель нулевой, и логично, что проверка
Но помимо указателя на объект в С++ есть еще указатель на члены класса.
Что здесь важно. Указатель на член класса - это не адрес в памяти. Это смещение относительно адреса объекта к адресу члена. Таким образом, указатель на первый член класса всегда будет 0.
Еще более занятно, что прошла проверка
Ну и тут можно догадаться, но мы выведем кодом ниже и чему равен nullptr для членов класса:
значение nullptr - это минус один. И это без всяких эзотерических архитектур, в рамках одной и той же программы у нас есть два разных значения для nullptr.
Весело в С++ однако.....
Более продвинутые говорят, что это зависит от имплементации, и приводят в пример какие-то embedded сценарии архитектур, до которых среднему программисту дела нет. Поэтому первая часть людей продолжает говорить, что в практическом поле nullptr - это ноль.
Но есть красивый пример, иллюстрирующий что это не так.
Природа заблуждения растет, как мне кажется, из проверки подстановкой указателя в if.
struct S {
char a;
char b;
int c;
int d;
};
int main()
{
auto s = S{'1', '2', 3, 4};
S* pS = nullptr;
if (!pS) {
std::cout << "pS" << std::endl;
std::cout << std::hex << "ptr: "<< *(int*)(&pS) << std::endl;
}
pS = &s;
if (pS) {
std::cout << "pS" << std::endl;
std::cout << std::hex << "ptr: "<< *(int*)(&pS) << std::endl;
}
}Ну тут все очевидно. Выведет вот это:
pS
ptr: 0
pS
ptr: 7ede1034
Указатель нулевой, и логично, что проверка
if (var) - это проверка на ноль, как мы и привыкли. Но помимо указателя на объект в С++ есть еще указатель на члены класса.
char S::* pSm = nullptr;
pSm = &S::a;
if (pSm) {
std::cout << "pS.a" << std::endl;
std::cout << std::dec << "value: " << s.*pSm << std::endl;
std::cout << std::hex << "ptr: "<< *(int*)(&pSm) << std::endl;
}
pSm = &S::b;
if (pSm) {
std::cout << "pS.b" << std::endl;
std::cout << std::dec << "value: " << s.*pSm << std::endl;
std::cout << std::hex << "ptr: "<< *(int*)(&pSm) << std::endl;
}
Что здесь важно. Указатель на член класса - это не адрес в памяти. Это смещение относительно адреса объекта к адресу члена. Таким образом, указатель на первый член класса всегда будет 0.
pS.a
value: 1
ptr: 0
pS.b
value: 2
ptr: 1
Еще более занятно, что прошла проверка
if (pSm) для нуля. Потому что это валидное и даже ожидаемое значение, а в бинарном коде эта проверка выглядит так:cmp rax, -1
Ну и тут можно догадаться, но мы выведем кодом ниже и чему равен nullptr для членов класса:
pSm = nullptr;
if (!pSm) {
std::cout << "pS.nullptr" << std::endl;
std::cout << std::dec << "value: " << s.*pSm << std::endl;
std::cout << std::hex << "ptr: "<< *(int*)(&pSm) << std::endl;
}
if (pSm) {
std::cout << "Unreacheable" << std::endl;
}
pS.nullptr
value:
ptr: ffffffff
значение nullptr - это минус один. И это без всяких эзотерических архитектур, в рамках одной и той же программы у нас есть два разных значения для nullptr.
Весело в С++ однако.....
godbolt.org
Compiler Explorer - C++
struct S {
char a;
char b;
int c;
int d;
};
int main()
{
auto s = S{'1', '2', 3, 4};
S* pS = nullptr;
if (!pS) {
std::cout << "pS" << std::endl;
std::cout << std::hex << "ptr: "<< *(int*)(&pS) << std::endl;
…
char a;
char b;
int c;
int d;
};
int main()
{
auto s = S{'1', '2', 3, 4};
S* pS = nullptr;
if (!pS) {
std::cout << "pS" << std::endl;
std::cout << std::hex << "ptr: "<< *(int*)(&pS) << std::endl;
…
🔥20👍1🤔1😢1
Если вы очень любите питон, у меня для вас выход:
P.S. тот, кто мне скинул этот код, ссылался на реальную лабораторную студента...
🤡🤡🤡🤡🤡🤡🤡🤡
#include <iostream>
#include <stdio.h>
#include <fcntl.h>
#include <string>
#define print(data) cout<<data<<endl;
#define ord(data) int(data[0])
#define str(data) char(data)
#define open fopen
#define write(f, data) fputc(data, f)
using namespace std;
string input()
{
string s;
cin>>s;
return s;
};
FILE* f;
int chr;
int main() {
print("Enter:");
f = open("code.txt", "w");
chr = ord(input());
print(chr);
write(f, str(chr));
return 0;
}
P.S. тот, кто мне скинул этот код, ссылался на реальную лабораторную студента...
🤡🤡🤡🤡🤡🤡🤡🤡
💊18😍4🏆4👍2🔥2😐1🙈1
Пример честно украденный отсюда.
Но я периодически возвращаюсь к нему, чтобы вновь попытаться понять, почему я до сих пор не бросил эти несчастные кресты.
Пример выглядит следующим образом:
В этом примере мы дважны создаем алиасы с помощью
А дальше проверяем подстановку типов: какой из using-ов будет подставлен.
И подсказка есть в самом коде: четыре разных компилятора дадут четыре разных ответа.
Вот тут версия с запуском программ. И до сих пор дают разные варианты ответа....
Автор оригинала утверждает, что поведение четко определено в стандарте, и прав только gcc, а остальные по-разному врут...
Я ему верю, но код на всякий перекрещу, и святой водой ноутбук побрызгаю...
Но я периодически возвращаюсь к нему, чтобы вновь попытаться понять, почему я до сих пор не бросил эти несчастные кресты.
Пример выглядит следующим образом:
struct A {
using T = T1;
using U = U1;
operator U1 T1::*();
operator U1 T2::*();
operator U2 T1::*();
operator U2 T2::*();
};
inline auto which(U1 T1::*) { return "gcc"; }
inline auto which(U1 T2::*) { return "icc"; }
inline auto which(U2 T1::*) { return "msvc"; }
inline auto which(U2 T2::*) { return "clang"; }
int main() {
A a;
using T = T2;
using U = U2;
puts(which(a.operator U T::*()));
}
В этом примере мы дважны создаем алиасы с помощью
using на четыре типа. Типы` U1` и U2, и типы их членов T1 и T2.А дальше проверяем подстановку типов: какой из using-ов будет подставлен.
И подсказка есть в самом коде: четыре разных компилятора дадут четыре разных ответа.
Вот тут версия с запуском программ. И до сих пор дают разные варианты ответа....
Автор оригинала утверждает, что поведение четко определено в стандарте, и прав только gcc, а остальные по-разному врут...
Я ему верю, но код на всякий перекрещу, и святой водой ноутбук побрызгаю...
Arthur O’Dwyer
Fun with conversion-operator name lookup
As of this writing (but perhaps not for very much longer!) the four mainstream compilers
on Godbolt Compiler Explorer give four different answers for
this simple C++ program:
on Godbolt Compiler Explorer give four different answers for
this simple C++ program:
😁11🗿5🔥2👍1🎉1