1. Недавно думал, почему пустая структура в C++ всегда весит один байт (стандарт требует, чтобы у двух различных объектов были различные адреса).
Давайте подумаем, что может сломаться, если так сделать.
Как в таком случае должны выглядеть массивы? По факту это будет n элементов (число n компилятор часто хранит слева от указателя), расположенных по одному адресу. Т.к. объект нулевого размера не хранит данных, а всю информацию о типе компилятор имеет, можно все вызовы функций делать от одного адреса (это никак на объект не повлияет). В случае, когда мы делаем
arr[i], компилятор может отдавать один и тот же элемент. Если объект используется как обёртка над какими-то операциями (в конструкторе/деструкторе), их вызов корректен (правда деструктор дважды по одному адресу это ub, но, предположим, закостылили). При итерировании по массиву мы помним число n, потому понимаем, когда пора остановиться, а когда начинается ub -> можем пытаться оптимизировать. По этим же причинам удаление массива -- операция конечная. Конечно возникают проблемы с реализацией end() для кастомных контейнеров, т.к. элемент, расположенный за последним, становится недостижимым, но условно можно обязать пользователей определять end() как arr + sizeof(char). На этом поиск проблем я остановил. Хотя есть ощущения, что сломается что-то гораздо более сложное (целый мир strict aliasing). Понятно, что как будто можно было бы в такое научиться, но язык и так очень нетривиален, чтобы ещё такие навороты делать. Хотя зачатки подобного уже есть (EBO/EBCO,
[[no_unique_address]], VLA/FAM о которых я уже упоминал). Вообще зачем об этом думать. Например я в последнее время часто использую такую структуру:
template <typename T>
struct To {};которая используется для tag dispatching. И раз такой аргумент нужен только для выбора перегрузки функции, можно было бы его оптимизировать в ноль байт. Как-то от этого и раскрутилась мысль.
Кстати Rust умеет как-то с типами нулевого размера работать: раз, два. Может какой-нибудь поклонник этого языка расскажет чуть больше в комментариях : )
2. Я думаю, все знакомы с правилом пяти. Оно гласит, что если вы определяете что-то одного из конструктор копирования/перемещения, аналогичных
operator= или деструктора, вы должны написать и все остальные. Это не требование компиляторов, а правило хорошего тона, иначе вступают в силу сложные правила того, как работает компилятор при генерации остальных методов. Интересно, что такой подход как будто противоречит single-responsibility principle, который говорит, что класс должен отвечать за что-то одно. Поинт в том, что если у вас определён хотя бы что-то одно из указанного, вы должны определить все пять == научить объект манипулировать данными ещё как-то кроме методов, поддерживающих некоторый инвариант класса.
Потому гораздо полезнее правило нуля: не писать никого вообще. И вы наверняка им пользовались. Если у вас в полях класса есть какие-то нетривиальные объекты, компилятор сам справится корректно их скопировать/переместить/удалить. Потому что, на самом деле, писать что-то из пяти этих особых методов вам нужно очень редко (чему конечно противоречит опыт обучения, когда мы постоянно переписываем какие-то (не)стандартные контейнеры и пишем подобное постоянно).
3. Чувак поясняет, почему double/float в любом языке программирования на самом деле не вещественные числа (спойлер: формально с точки зрения математики числа в компьютере не могут представить все вещественные числа). Статью заминусили. Как мне кажется, вполне справедливо, потому что если все в мире понимают о чём речь, зачем воду мутить?
4. Известный факт, что можно объявлять структуры/классы в функциях/методах. Но почему-то нельзя так же объявить шаблон структуры/класса. Причин никаких это запрещать не обнаружил. Даже нашёл бумагу с исправлением: link.