C++: Хроники Дурки🚑
915 subscribers
6 photos
53 links
Очень люблю C++, но это скорее уже стокгольмский синдром.
Постоянно нахожу способы стрельнуть себе в ногу.
Download Telegram
Немного про новые стандарты: когда я показываю какую-то упячку, часто мне говорят "просто не пользуйся вот этим...".
Например, "не пользуйся 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. Прекрасно!
🥴14🔥3😢32🤩1
Ну а как еще можно было назвать канал, как не "Дурка"?

Ну сам факт, что вот такой код компилируется, запускается, а еще и не падает, и выводит 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();
}


Нет, конечно, понятно даже почему, если разобрать поднаготную устройства функций-членов класса. Но блииииин........
💊9😨31👍1👎1😢1
Еще одна абсолютно бесполезная, но унаследнованная штука в С++. Заголовок может заинклюдить сам себя.


Например вот такой заголовок:


// 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🥴21👍1
Причины, по которым этот канал называется "дурка".

Вот такой вот метод:


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;
}


У меня все, увидимся в дурдоме
🤡10🤝5💊3🤷‍♂21
Ну, к разного рода наркомании, что


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


Вот какие-то такие развлечения....
👏11😁9🥴61
Есть задачка, абсолютно практическая на самом деле.

Все знают про 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 фейковыми байтами.


Это классика, но вопрос не про это. Утверждение:

Размер структуры невозможно поменять, переставив переменные в ней В ОБРАТНОМ ПОРЯДКЕ


Задача - доказать что утверждение верно или привести контрпример.

Как я сказал, задача на самом деле практическая, подробности в следующем посте.
Если вы очень любите питон, у меня для вас выход:


#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
А вот в C23 можно тип объявлять прямо в return типе.


#include <stdio.h>

struct{int a; float b;} test()
{
return (typeof(test())){1337, 666.666};
}

int main()
{
auto a = test();
printf("%d %f\n", a.a, a.b);
return 0;
}


А вот в С++ такого нельзя:


error: new types may not be defined in a return type


Товарищи из комитета, отстаете. Где эти безусловно нужные всем языковые фичи?
💊17🤣6🫡5🔥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;
}
};


И вот не понятно, толи все логично, и я придераюсь. Толи правда неплохо бы генерировать операторы сравнения для пустых структур, а явно прописовать требовать только когда мы хотим их явно запретить. Я не знаю, я не понимаю...
🤔7😁32
И снова спасибо подписчикам за отборный контент.

Оказывается, в С++ можно объявить оператор каста к... 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;
}


И вот это уже вообще взрыв мозга! 🤯
😁21💊11🔥1