Итак, один из любимых примеров дурки.
Мы заводим структуру данных, которая конструируется от аргумента типа double. И вторую структуру, которая конструируется от первой.
А дальше создадим переменную второго типа.
Проигнорируем ворнинги компиляторов (кого оно волнует, компилируется же).
А что будет, если мы выведем значение перменной
Внезапно, выведет 1:
Чта?!
Ну, пришло время почитать ворнинги. И выясняем, что вот это:
не переменная. Это - функция. Дело в том, что вот два таких объявления функций, в целом, одинаковые:
Мы вполне легально можем аргументы функции заключать в скобки при объявлении функций... А все, что может быть трактовано в С++ как объявление функции, должно быть трактовано как объявление функции.
И.... Я не знаю, как оно так в итоге так получается, но стреляет время от времени...
Добро пожаловать к нам в дурку.
Мы заводим структуру данных, которая конструируется от аргумента типа double. И вторую структуру, которая конструируется от первой.
А дальше создадим переменную второго типа.
#include<iostream>
struct A {
double v;
explicit A(double d) : v(d) {}
};
struct B {
double v;
explicit B(A a) : v(a.v) {}
};
int main(){
double d = 3.14;
B b(A(d));
}
Проигнорируем ворнинги компиляторов (кого оно волнует, компилируется же).
А что будет, если мы выведем значение перменной
b?int main(){
double d = 3.14;
B b(A(d));
std::cout << b << std::endl;
}Внезапно, выведет 1:
Program returned: 0
1
Чта?!
Ну, пришло время почитать ворнинги. И выясняем, что вот это:
B b(A(d));
не переменная. Это - функция. Дело в том, что вот два таких объявления функций, в целом, одинаковые:
void f(int a);
void q(int(a));
Мы вполне легально можем аргументы функции заключать в скобки при объявлении функций... А все, что может быть трактовано в С++ как объявление функции, должно быть трактовано как объявление функции.
И.... Я не знаю, как оно так в итоге так получается, но стреляет время от времени...
Добро пожаловать к нам в дурку.
godbolt.org
Compiler Explorer - C++
struct A {
double v;
explicit A(double d) : v(d) {}
};
struct B {
double v;
explicit B(A a) : v(a.v) {}
};
int main(){
double d = 3.14;
B b(A(d));
}
double v;
explicit A(double d) : v(d) {}
};
struct B {
double v;
explicit B(A a) : v(a.v) {}
};
int main(){
double d = 3.14;
B b(A(d));
}
🤯11❤5
Приколько поговорить про гадость, которую можно отловить на этапе компиляции.
Но есть разная дурка, которая аффектит рантайм, но проходит мимо всех ворнингов (иногда это ловят всякие clang-tidy и прочие PVS студии).
Вот канонический пример:
Мы создаем мапу (она будет создана в компайл тайме), а потом все объекты из нее эффективно муваются.
Но если мы внимательно посмотрим в исхродный код, то найдем там вот такое:
Я прошу прощения...? Какое копирование?! Где?
И вообще весь блок выглядит подозрительно похоже на конструктор копирования:
Не буду тут показывать, но, если провести перфоманс тесты, мы тоже увидим, что тут все тормозит.
И если детально разобраться: так и есть - это конструктор копирования.
Проблема вот в этой функции:
Если поменять объявление этой функции на
то этот блок строк на 70 машинного кода уйдет, а перфоманс выровняется.
Почему так? Если посмотреть на cpp reference, то мы увидим, что
значение в мапе содержит константный ключ. А конвертировать из структуры с константным ключем в структуру с неконстантным ключом.... Это копирование!...
И ни одного ворнинга! Мой же ты любимый С++.
Но есть разная дурка, которая аффектит рантайм, но проходит мимо всех ворнингов (иногда это ловят всякие clang-tidy и прочие PVS студии).
Вот канонический пример:
#include<iostream>
#include <map>
#include <vector>
#include <string>
using namespace std;
using Vec = vector<string>;
void f(pair<string, Vec>&& arg) {
for (const auto& v: arg.second) {
puts(v.c_str());
}
}
int main(){
auto m = map<string, Vec>{
{"first", {"1","2"}},
{"second", {"3","4","5"}},
};
for (auto&& p: m) {
f(std::move(p));
}
}
Мы создаем мапу (она будет создана в компайл тайме), а потом все объекты из нее эффективно муваются.
Но если мы внимательно посмотрим в исхродный код, то найдем там вот такое:
call memcpy@PLT
Я прошу прощения...? Какое копирование?! Где?
И вообще весь блок выглядит подозрительно похоже на конструктор копирования:
.LBB1_84:
mov qword ptr [rsp + 56], r13
mov r14, qword ptr [rbx + 32]
mov rbp, qword ptr [rbx + 40]
cmp rbp, 16
jb .LBB1_87
lea r15, [rbp + 1]
mov rdi, r15
call operator new(unsigned long)@PLT
mov qword ptr [rsp + 56], rax
mov qword ptr [rsp + 72], rbp
jmp .LBB1_89
.LBB1_87:
test rbp, rbp
je .LBB1_106
lea r15, [rbp + 1]
mov rax, r13
.LBB1_89:
mov rdi, rax
mov rsi, r14
mov rdx, r15
call memcpy@PLT
.LBB1_90:
mov qword ptr [rsp + 64], rbp
mov r14, qword ptr [rbx + 64]
mov qword ptr [rsp + 88], r14
movups xmm0, xmmword ptr [rbx + 72]
mov r15, qword ptr [rbx + 72]
movups xmmword ptr [rsp + 96], xmm0
xorps xmm0, xmm0
movups xmmword ptr [rbx + 64], xmm0
mov qword ptr [rbx + 80], 0
cmp r14, r15
je .LBB1_98
Не буду тут показывать, но, если провести перфоманс тесты, мы тоже увидим, что тут все тормозит.
И если детально разобраться: так и есть - это конструктор копирования.
Проблема вот в этой функции:
void f(pair<string, Vec>&& arg)
Если поменять объявление этой функции на
void f(pair<const string, Vec>&& arg) {то этот блок строк на 70 машинного кода уйдет, а перфоманс выровняется.
Почему так? Если посмотреть на cpp reference, то мы увидим, что
key_type Key
mapped_type T
value_type std::pair<const Key, T>
значение в мапе содержит константный ключ. А конвертировать из структуры с константным ключем в структуру с неконстантным ключом.... Это копирование!...
И ни одного ворнинга! Мой же ты любимый С++.
godbolt.org
Compiler Explorer - C++
using namespace std;
using Vec = vector<string>;
void f(pair<string, Vec>&& arg) {
for (const auto& v: arg.second) {
puts(v.c_str());
}
}
int main(){
auto m = map<string, Vec>{
{"first", {"1","2"}},
{"second", {"3","4"…
using Vec = vector<string>;
void f(pair<string, Vec>&& arg) {
for (const auto& v: arg.second) {
puts(v.c_str());
}
}
int main(){
auto m = map<string, Vec>{
{"first", {"1","2"}},
{"second", {"3","4"…
👍11😐5🔥3🗿2👌1
Внимание, шутка!!
Умножение в С++ некоммутативно.
Вот пример:
Выведет 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
Ну а как еще можно было назвать канал, как не "Дурка"?
Ну сам факт, что вот такой код компилируется, запускается, а еще и не падает, и выводит 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
Еще одна абсолютно бесполезная, но унаследнованная штука в С++. Заголовок может заинклюдить сам себя.
Например вот такой заголовок:
Сработает совершенно нормально.
Ни ворнингов, ничего. А самое главное - как бы так передефайнить разные куски, чтобы на этом механизме устроить перебор? Ну, перебрать все комбинации из 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
Причины, по которым этот канал называется "дурка".
Вот такой вот метод:
Компиляторы сожрут. И не выведут ни одного ворнинга при 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
Ну, к разного рода наркомании, что
Это одно и то же - все уже привыкли.
А как насчет инициализации массива с указанием ренжа индексов?
Типа:
Или инициализации единицы для вайтспейстов:
Это нормально без дополнительных выкрутасов работает на 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
Есть задачка, абсолютно практическая на самом деле.
Все знают про 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 фейковыми байтами.
Это классика, но вопрос не про это. Утверждение:
Размер структуры невозможно поменять, переставив переменные в ней В ОБРАТНОМ ПОРЯДКЕ
Задача - доказать что утверждение верно или привести контрпример.
Как я сказал, задача на самом деле практическая, подробности в следующем посте.
Если вы очень любите питон, у меня для вас выход:
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
А вот в 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
Товарищи из комитета, отстаете. Где эти безусловно нужные всем языковые фичи?
godbolt.org
Compiler Explorer - C (x86-64 clang (trunk))
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;
}
{
return (typeof(test())){1337, 666.666};
}
int main()
{
auto a = test();
printf("%d %f\n", a.a, a.b);
return 0;
}
💊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;
}
};
И вот не понятно, толи все логично, и я придераюсь. Толи правда неплохо бы генерировать операторы сравнения для пустых структур, а явно прописовать требовать только когда мы хотим их явно запретить. Я не знаю, я не понимаю...
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
Сегодня разбираем вот такой рабочий пример. (Рабочий в том смысле, что найден на работе).
Берем вот такой код:
Внимательно на него смотрим. Потом смотрим еще внимательнее.
Не видим проблемы.
Смотрим еще раз, и опять не видим проблемы.
А потом отправляем его на компиляцию, и получаем ошибку:
У нас нет инстанса хеша для строки.
😣😣😣😣😣😣
Как это исправляется? Правильно, заголовок строки надо обязательно ставить выше заголовка мапы:
Точнее на самом деле, разумеется, важен не порядок заголовков (хотя там отдельный геморой. И в большинстве 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
Прекрасный и интуитивный 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
Посмеемся?
Что выведет?
Ответ конечно `bool`:
Почему так?
Потому что перегрузки из Base в Derived скрываются целиком, если в наследнике появился метод с тем же именем.
И дальше d.Set("hello") уже ищет только среди перегрузок Derived.
А const char* в bool конвертируется просто замечательно.
И по нашей любимой традиции - ни одного ворнинга ни в одном из компиляторов.
#include <iostream>
#include <string_view>
struct Base {
void Set(std::string_view) { std::cout << "string\n"; }
void Set(int) { std::cout << "int\n"; }
};
struct Derived : Base {
void Set(bool) { std::cout << "bool\n"; }
};
int main() {
Derived d;
d.Set("hello");
}
Что выведет?
Почему так?
Потому что перегрузки из Base в Derived скрываются целиком, если в наследнике появился метод с тем же именем.
И дальше d.Set("hello") уже ищет только среди перегрузок Derived.
А const char* в bool конвертируется просто замечательно.
И по нашей любимой традиции - ни одного ворнинга ни в одном из компиляторов.
godbolt.org
Compiler Explorer - C++
struct Base {
void Set(std::string_view) { std::cout << "string\n"; }
void Set(int) { std::cout << "int\n"; }
};
struct Derived : Base {
void Set(bool) { std::cout << "bool\n"; }
};
int main() {
Derived d;
d.Set("hello");…
void Set(std::string_view) { std::cout << "string\n"; }
void Set(int) { std::cout << "int\n"; }
};
struct Derived : Base {
void Set(bool) { std::cout << "bool\n"; }
};
int main() {
Derived d;
d.Set("hello");…
🔥23😁10☃2❤1
Сегодняшняя рубрика называется "обычный шаблонный код, который компилируется только после жертвоприношения".
Что выведет вот этот код?
Иииииии... Правильный отвееееет.....
Да, вы правы, он ничего не выведет.
Упадет на ошибке компиляции:
```
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>…
❤27🍌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